@wingman-ai/gateway 0.4.0 → 0.4.2

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.
Files changed (104) hide show
  1. package/README.md +29 -111
  2. package/dist/agent/config/agentConfig.cjs +2 -0
  3. package/dist/agent/config/agentConfig.d.ts +6 -0
  4. package/dist/agent/config/agentConfig.js +2 -0
  5. package/dist/agent/config/agentLoader.cjs +21 -18
  6. package/dist/agent/config/agentLoader.js +22 -19
  7. package/dist/agent/config/toolRegistry.cjs +19 -0
  8. package/dist/agent/config/toolRegistry.d.ts +4 -0
  9. package/dist/agent/config/toolRegistry.js +17 -1
  10. package/dist/agent/middleware/additional-messages.cjs +115 -11
  11. package/dist/agent/middleware/additional-messages.d.ts +9 -0
  12. package/dist/agent/middleware/additional-messages.js +115 -11
  13. package/dist/agent/tests/agentLoader.test.cjs +45 -0
  14. package/dist/agent/tests/agentLoader.test.js +45 -0
  15. package/dist/agent/tests/toolRegistry.test.cjs +2 -0
  16. package/dist/agent/tests/toolRegistry.test.js +2 -0
  17. package/dist/agent/tools/node_invoke.cjs +146 -0
  18. package/dist/agent/tools/node_invoke.d.ts +86 -0
  19. package/dist/agent/tools/node_invoke.js +109 -0
  20. package/dist/cli/commands/gateway.cjs +1 -1
  21. package/dist/cli/commands/gateway.js +1 -1
  22. package/dist/cli/commands/init.cjs +135 -1
  23. package/dist/cli/commands/init.js +136 -2
  24. package/dist/cli/commands/skill.cjs +7 -3
  25. package/dist/cli/commands/skill.js +7 -3
  26. package/dist/cli/config/loader.cjs +7 -3
  27. package/dist/cli/config/loader.js +7 -3
  28. package/dist/cli/config/schema.cjs +27 -9
  29. package/dist/cli/config/schema.d.ts +18 -4
  30. package/dist/cli/config/schema.js +23 -8
  31. package/dist/cli/core/agentInvoker.cjs +70 -14
  32. package/dist/cli/core/agentInvoker.d.ts +10 -0
  33. package/dist/cli/core/agentInvoker.js +70 -14
  34. package/dist/cli/services/skillRepository.cjs +155 -69
  35. package/dist/cli/services/skillRepository.d.ts +7 -2
  36. package/dist/cli/services/skillRepository.js +155 -69
  37. package/dist/cli/services/skillService.cjs +93 -26
  38. package/dist/cli/services/skillService.d.ts +7 -0
  39. package/dist/cli/services/skillService.js +96 -29
  40. package/dist/cli/types/skill.d.ts +8 -3
  41. package/dist/gateway/http/nodes.cjs +247 -0
  42. package/dist/gateway/http/nodes.d.ts +20 -0
  43. package/dist/gateway/http/nodes.js +210 -0
  44. package/dist/gateway/node.cjs +10 -1
  45. package/dist/gateway/node.d.ts +10 -1
  46. package/dist/gateway/node.js +10 -1
  47. package/dist/gateway/server.cjs +414 -27
  48. package/dist/gateway/server.d.ts +34 -0
  49. package/dist/gateway/server.js +408 -27
  50. package/dist/gateway/types.d.ts +6 -1
  51. package/dist/gateway/validation.cjs +2 -0
  52. package/dist/gateway/validation.d.ts +4 -0
  53. package/dist/gateway/validation.js +2 -0
  54. package/dist/skills/activation.cjs +92 -0
  55. package/dist/skills/activation.d.ts +12 -0
  56. package/dist/skills/activation.js +58 -0
  57. package/dist/skills/bin-requirements.cjs +63 -0
  58. package/dist/skills/bin-requirements.d.ts +3 -0
  59. package/dist/skills/bin-requirements.js +26 -0
  60. package/dist/skills/metadata.cjs +141 -0
  61. package/dist/skills/metadata.d.ts +29 -0
  62. package/dist/skills/metadata.js +104 -0
  63. package/dist/skills/overlay.cjs +75 -0
  64. package/dist/skills/overlay.d.ts +2 -0
  65. package/dist/skills/overlay.js +38 -0
  66. package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
  67. package/dist/tests/additionalMessageMiddleware.test.js +92 -0
  68. package/dist/tests/cli-config-loader.test.cjs +7 -3
  69. package/dist/tests/cli-config-loader.test.js +7 -3
  70. package/dist/tests/cli-init.test.cjs +54 -0
  71. package/dist/tests/cli-init.test.js +54 -0
  72. package/dist/tests/config-json-schema.test.cjs +12 -0
  73. package/dist/tests/config-json-schema.test.js +12 -0
  74. package/dist/tests/gateway-http-security.test.cjs +277 -0
  75. package/dist/tests/gateway-http-security.test.d.ts +1 -0
  76. package/dist/tests/gateway-http-security.test.js +271 -0
  77. package/dist/tests/gateway-node-mode.test.cjs +174 -0
  78. package/dist/tests/gateway-node-mode.test.d.ts +1 -0
  79. package/dist/tests/gateway-node-mode.test.js +168 -0
  80. package/dist/tests/gateway-origin-policy.test.cjs +60 -0
  81. package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
  82. package/dist/tests/gateway-origin-policy.test.js +54 -0
  83. package/dist/tests/gateway.test.cjs +1 -0
  84. package/dist/tests/gateway.test.js +1 -0
  85. package/dist/tests/node-tools.test.cjs +77 -0
  86. package/dist/tests/node-tools.test.d.ts +1 -0
  87. package/dist/tests/node-tools.test.js +71 -0
  88. package/dist/tests/nodes-api.test.cjs +86 -0
  89. package/dist/tests/nodes-api.test.d.ts +1 -0
  90. package/dist/tests/nodes-api.test.js +80 -0
  91. package/dist/tests/skill-activation.test.cjs +86 -0
  92. package/dist/tests/skill-activation.test.d.ts +1 -0
  93. package/dist/tests/skill-activation.test.js +80 -0
  94. package/dist/tests/skill-metadata.test.cjs +119 -0
  95. package/dist/tests/skill-metadata.test.d.ts +1 -0
  96. package/dist/tests/skill-metadata.test.js +113 -0
  97. package/dist/tests/skill-repository.test.cjs +363 -0
  98. package/dist/tests/skill-repository.test.js +363 -0
  99. package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
  100. package/dist/webui/index.html +1 -1
  101. package/package.json +4 -4
  102. package/skills/gog/SKILL.md +1 -1
  103. package/skills/weather/SKILL.md +1 -1
  104. package/skills/ui-registry/SKILL.md +0 -35
@@ -59,6 +59,8 @@ const hooks_cjs_namespaceObject = require("../../agent/middleware/hooks.cjs");
59
59
  const media_compat_cjs_namespaceObject = require("../../agent/middleware/media-compat.cjs");
60
60
  const terminal_session_manager_cjs_namespaceObject = require("../../agent/tools/terminal_session_manager.cjs");
61
61
  const uiRegistry_cjs_namespaceObject = require("../../agent/uiRegistry.cjs");
62
+ const activation_cjs_namespaceObject = require("../../skills/activation.cjs");
63
+ const overlay_cjs_namespaceObject = require("../../skills/overlay.cjs");
62
64
  const agentLoader_cjs_namespaceObject = require("../../agent/config/agentLoader.cjs");
63
65
  const loader_cjs_namespaceObject = require("../config/loader.cjs");
64
66
  function _define_property(obj, key, value) {
@@ -322,6 +324,7 @@ class AgentInvoker {
322
324
  let activeToolName = null;
323
325
  let lastToolName = null;
324
326
  let rootLangGraphRunId;
327
+ const skillOverlayDirectories = [];
325
328
  const isCancelled = ()=>options?.signal?.aborted === true;
326
329
  try {
327
330
  const hookSessionId = sessionId || (0, external_uuid_namespaceObject.v4)();
@@ -329,7 +332,9 @@ class AgentInvoker {
329
332
  const effectiveWorkdir = this.workdir ? executionWorkspace : null;
330
333
  const loader = new agentLoader_cjs_namespaceObject.AgentLoader(this.configDir, this.workspace, this.wingmanConfig, executionWorkspace, {
331
334
  terminalOwnerId: `${agentName}:${hookSessionId}`,
332
- terminalSessionManager: this.terminalSessionManager
335
+ terminalSessionManager: this.terminalSessionManager,
336
+ nodeInvoker: this.nodeInvoker,
337
+ nodeDefaultTargetClientId: this.nodeDefaultTargetClientId
333
338
  });
334
339
  const targetAgent = await loader.loadAgent(agentName);
335
340
  if (!targetAgent) throw new Error(`Agent "${agentName}" not found`);
@@ -375,7 +380,10 @@ class AgentInvoker {
375
380
  defaultOutputDir: this.defaultOutputDir,
376
381
  outputVirtualPath: outputMount.virtualPath,
377
382
  dynamicUiEnabled: this.wingmanConfig?.gateway?.dynamicUiEnabled !== false,
378
- skillsDirectory
383
+ skillsDirectory,
384
+ nodeConnectedIdsProvider: this.nodeConnectedIdsProvider,
385
+ nodeConnectedTargetsProvider: this.nodeConnectedTargetsProvider,
386
+ defaultNodeTargetClientId: this.nodeDefaultTargetClientId
379
387
  })
380
388
  ];
381
389
  const summarizationSettings = resolveSummarizationMiddlewareSettings(this.wingmanConfig);
@@ -407,9 +415,8 @@ class AgentInvoker {
407
415
  }
408
416
  const checkpointer = this.sessionManager?.getCheckpointer();
409
417
  const bundledSkillsPath = (0, uiRegistry_cjs_namespaceObject.getBundledSkillsPath)();
418
+ const workspaceSkillsPath = (0, external_node_path_namespaceObject.join)(this.workspace, normalizedSkillsDirectory);
410
419
  const skillsSources = [];
411
- if ((0, external_node_fs_namespaceObject.existsSync)(bundledSkillsPath)) skillsSources.push("/skills-bundled/");
412
- skillsSources.push(skillsVirtualPath);
413
420
  const backendOverrides = {
414
421
  "/memories/": new external_deepagents_namespaceObject.FilesystemBackend({
415
422
  rootDir: (0, external_node_path_namespaceObject.join)(this.workspace, this.configDir, "memories"),
@@ -425,15 +432,46 @@ class AgentInvoker {
425
432
  rootDir: executionWorkspace,
426
433
  virtualMode: true
427
434
  });
428
- const workspaceSkillsPath = (0, external_node_path_namespaceObject.join)(this.workspace, normalizedSkillsDirectory);
429
- if ((0, external_node_fs_namespaceObject.existsSync)(workspaceSkillsPath)) backendOverrides[skillsVirtualPath] = new external_deepagents_namespaceObject.FilesystemBackend({
430
- rootDir: workspaceSkillsPath,
431
- virtualMode: true
432
- });
433
- if ((0, external_node_fs_namespaceObject.existsSync)(bundledSkillsPath)) backendOverrides["/skills-bundled/"] = new external_deepagents_namespaceObject.FilesystemBackend({
434
- rootDir: bundledSkillsPath,
435
- virtualMode: true
436
- });
435
+ if ((0, external_node_fs_namespaceObject.existsSync)(workspaceSkillsPath)) {
436
+ const workspaceActivation = await (0, activation_cjs_namespaceObject.resolveSkillActivation)(workspaceSkillsPath);
437
+ if (workspaceActivation.inactiveSkills.length > 0) {
438
+ const summary = workspaceActivation.inactiveSkills.map((entry)=>`${entry.name} (missing: ${entry.missingBins.join(", ")})`).join("; ");
439
+ this.logger.info(`Inactive workspace skills: ${summary}`);
440
+ }
441
+ let workspaceOverlayRoot = workspaceSkillsPath;
442
+ try {
443
+ const workspaceOverlay = await (0, overlay_cjs_namespaceObject.createSkillOverlayDirectory)(workspaceSkillsPath, workspaceActivation.activeSkillNames);
444
+ workspaceOverlayRoot = workspaceOverlay;
445
+ skillOverlayDirectories.push(workspaceOverlay);
446
+ } catch (error) {
447
+ this.logger.debug("Failed to build workspace skill overlay; falling back to unfiltered skills directory", error);
448
+ }
449
+ backendOverrides[skillsVirtualPath] = new external_deepagents_namespaceObject.FilesystemBackend({
450
+ rootDir: workspaceOverlayRoot,
451
+ virtualMode: true
452
+ });
453
+ skillsSources.push(skillsVirtualPath);
454
+ }
455
+ if ((0, external_node_fs_namespaceObject.existsSync)(bundledSkillsPath)) {
456
+ const bundledActivation = await (0, activation_cjs_namespaceObject.resolveSkillActivation)(bundledSkillsPath);
457
+ if (bundledActivation.inactiveSkills.length > 0) {
458
+ const summary = bundledActivation.inactiveSkills.map((entry)=>`${entry.name} (missing: ${entry.missingBins.join(", ")})`).join("; ");
459
+ this.logger.info(`Inactive bundled skills: ${summary}`);
460
+ }
461
+ let bundledOverlayRoot = bundledSkillsPath;
462
+ try {
463
+ const bundledOverlay = await (0, overlay_cjs_namespaceObject.createSkillOverlayDirectory)(bundledSkillsPath, bundledActivation.activeSkillNames);
464
+ bundledOverlayRoot = bundledOverlay;
465
+ skillOverlayDirectories.push(bundledOverlay);
466
+ } catch (error) {
467
+ this.logger.debug("Failed to build bundled skill overlay; falling back to unfiltered skills directory", error);
468
+ }
469
+ backendOverrides["/skills-bundled/"] = new external_deepagents_namespaceObject.FilesystemBackend({
470
+ rootDir: bundledOverlayRoot,
471
+ virtualMode: true
472
+ });
473
+ skillsSources.push("/skills-bundled/");
474
+ }
437
475
  if (outputMount.virtualPath && outputMount.absolutePath) backendOverrides[outputMount.virtualPath] = new external_deepagents_namespaceObject.FilesystemBackend({
438
476
  rootDir: outputMount.absolutePath,
439
477
  virtualMode: true
@@ -571,6 +609,11 @@ class AgentInvoker {
571
609
  this.outputManager.emitAgentError(errorWithToolContext);
572
610
  throw error;
573
611
  } finally{
612
+ for (const overlayDirectory of skillOverlayDirectories)try {
613
+ await (0, overlay_cjs_namespaceObject.removeSkillOverlayDirectory)(overlayDirectory);
614
+ } catch (error) {
615
+ this.logger.debug(`Failed to cleanup skill overlay directory ${overlayDirectory}`, error);
616
+ }
574
617
  if (this.mcpManager) {
575
618
  this.logger.debug("Cleaning up MCP client");
576
619
  await this.mcpManager.cleanup();
@@ -602,6 +645,10 @@ class AgentInvoker {
602
645
  _define_property(this, "workdir", null);
603
646
  _define_property(this, "defaultOutputDir", null);
604
647
  _define_property(this, "mcpProxyConfig", void 0);
648
+ _define_property(this, "nodeInvoker", void 0);
649
+ _define_property(this, "nodeDefaultTargetClientId", void 0);
650
+ _define_property(this, "nodeConnectedIdsProvider", void 0);
651
+ _define_property(this, "nodeConnectedTargetsProvider", void 0);
605
652
  this.outputManager = options.outputManager;
606
653
  this.logger = options.logger;
607
654
  this.workspace = options.workspace || process.cwd();
@@ -611,9 +658,18 @@ class AgentInvoker {
611
658
  this.workdir = options.workdir || null;
612
659
  this.defaultOutputDir = options.defaultOutputDir || null;
613
660
  this.mcpProxyConfig = options.mcpProxyConfig;
661
+ this.nodeInvoker = options.nodeInvoker;
662
+ this.nodeDefaultTargetClientId = options.nodeDefaultTargetClientId;
663
+ this.nodeConnectedIdsProvider = options.nodeConnectedIdsProvider;
664
+ this.nodeConnectedTargetsProvider = options.nodeConnectedTargetsProvider;
614
665
  const configLoader = new loader_cjs_namespaceObject.WingmanConfigLoader(this.configDir, this.workspace);
615
666
  this.wingmanConfig = configLoader.loadConfig();
616
- this.loader = new agentLoader_cjs_namespaceObject.AgentLoader(this.configDir, this.workspace, this.wingmanConfig);
667
+ this.loader = new agentLoader_cjs_namespaceObject.AgentLoader(this.configDir, this.workspace, this.wingmanConfig, this.workspace, {
668
+ terminalOwnerId: "default",
669
+ terminalSessionManager: this.terminalSessionManager,
670
+ nodeInvoker: this.nodeInvoker,
671
+ nodeDefaultTargetClientId: this.nodeDefaultTargetClientId
672
+ });
617
673
  }
618
674
  }
619
675
  function buildUserContent(prompt, attachments, model) {
@@ -1,6 +1,8 @@
1
1
  import type { WingmanAgentConfig } from "@/agent/config/agentConfig.js";
2
2
  import { type MCPProxyConfig } from "@/agent/config/mcpClientManager.js";
3
+ import { type ConnectedNodeTarget } from "@/agent/middleware/additional-messages.js";
3
4
  import { type TerminalSessionManager } from "@/agent/tools/terminal_session_manager.js";
5
+ import type { NodeInvokeRequest, NodeInvokeResult } from "@/agent/tools/node_invoke.js";
4
6
  import type { WingmanAgent } from "@/types/agents.js";
5
7
  import type { Logger } from "../../logger.js";
6
8
  import type { WingmanConfigType } from "../config/schema.js";
@@ -16,6 +18,10 @@ export interface AgentInvokerOptions {
16
18
  workdir?: string | null;
17
19
  defaultOutputDir?: string | null;
18
20
  mcpProxyConfig?: MCPProxyConfig;
21
+ nodeInvoker?: (request: NodeInvokeRequest) => Promise<NodeInvokeResult>;
22
+ nodeDefaultTargetClientId?: string;
23
+ nodeConnectedIdsProvider?: () => string[] | Promise<string[]>;
24
+ nodeConnectedTargetsProvider?: () => ConnectedNodeTarget[] | Promise<ConnectedNodeTarget[]>;
19
25
  }
20
26
  export interface InvokeAgentOptions {
21
27
  signal?: AbortSignal;
@@ -152,6 +158,10 @@ export declare class AgentInvoker {
152
158
  private workdir;
153
159
  private defaultOutputDir;
154
160
  private mcpProxyConfig;
161
+ private nodeInvoker;
162
+ private nodeDefaultTargetClientId;
163
+ private nodeConnectedIdsProvider;
164
+ private nodeConnectedTargetsProvider;
155
165
  constructor(options: AgentInvokerOptions);
156
166
  findAllAgents(): WingmanAgentConfig[];
157
167
  /**
@@ -10,6 +10,8 @@ import { createHooksMiddleware } from "../../agent/middleware/hooks.js";
10
10
  import { mediaCompatibilityMiddleware } from "../../agent/middleware/media-compat.js";
11
11
  import { getSharedTerminalSessionManager } from "../../agent/tools/terminal_session_manager.js";
12
12
  import { getBundledSkillsPath } from "../../agent/uiRegistry.js";
13
+ import { resolveSkillActivation } from "../../skills/activation.js";
14
+ import { createSkillOverlayDirectory, removeSkillOverlayDirectory } from "../../skills/overlay.js";
13
15
  import { AgentLoader } from "../../agent/config/agentLoader.js";
14
16
  import { WingmanConfigLoader } from "../config/loader.js";
15
17
  function _define_property(obj, key, value) {
@@ -273,6 +275,7 @@ class AgentInvoker {
273
275
  let activeToolName = null;
274
276
  let lastToolName = null;
275
277
  let rootLangGraphRunId;
278
+ const skillOverlayDirectories = [];
276
279
  const isCancelled = ()=>options?.signal?.aborted === true;
277
280
  try {
278
281
  const hookSessionId = sessionId || v4();
@@ -280,7 +283,9 @@ class AgentInvoker {
280
283
  const effectiveWorkdir = this.workdir ? executionWorkspace : null;
281
284
  const loader = new AgentLoader(this.configDir, this.workspace, this.wingmanConfig, executionWorkspace, {
282
285
  terminalOwnerId: `${agentName}:${hookSessionId}`,
283
- terminalSessionManager: this.terminalSessionManager
286
+ terminalSessionManager: this.terminalSessionManager,
287
+ nodeInvoker: this.nodeInvoker,
288
+ nodeDefaultTargetClientId: this.nodeDefaultTargetClientId
284
289
  });
285
290
  const targetAgent = await loader.loadAgent(agentName);
286
291
  if (!targetAgent) throw new Error(`Agent "${agentName}" not found`);
@@ -326,7 +331,10 @@ class AgentInvoker {
326
331
  defaultOutputDir: this.defaultOutputDir,
327
332
  outputVirtualPath: outputMount.virtualPath,
328
333
  dynamicUiEnabled: this.wingmanConfig?.gateway?.dynamicUiEnabled !== false,
329
- skillsDirectory
334
+ skillsDirectory,
335
+ nodeConnectedIdsProvider: this.nodeConnectedIdsProvider,
336
+ nodeConnectedTargetsProvider: this.nodeConnectedTargetsProvider,
337
+ defaultNodeTargetClientId: this.nodeDefaultTargetClientId
330
338
  })
331
339
  ];
332
340
  const summarizationSettings = resolveSummarizationMiddlewareSettings(this.wingmanConfig);
@@ -358,9 +366,8 @@ class AgentInvoker {
358
366
  }
359
367
  const checkpointer = this.sessionManager?.getCheckpointer();
360
368
  const bundledSkillsPath = getBundledSkillsPath();
369
+ const workspaceSkillsPath = join(this.workspace, normalizedSkillsDirectory);
361
370
  const skillsSources = [];
362
- if (existsSync(bundledSkillsPath)) skillsSources.push("/skills-bundled/");
363
- skillsSources.push(skillsVirtualPath);
364
371
  const backendOverrides = {
365
372
  "/memories/": new FilesystemBackend({
366
373
  rootDir: join(this.workspace, this.configDir, "memories"),
@@ -376,15 +383,46 @@ class AgentInvoker {
376
383
  rootDir: executionWorkspace,
377
384
  virtualMode: true
378
385
  });
379
- const workspaceSkillsPath = join(this.workspace, normalizedSkillsDirectory);
380
- if (existsSync(workspaceSkillsPath)) backendOverrides[skillsVirtualPath] = new FilesystemBackend({
381
- rootDir: workspaceSkillsPath,
382
- virtualMode: true
383
- });
384
- if (existsSync(bundledSkillsPath)) backendOverrides["/skills-bundled/"] = new FilesystemBackend({
385
- rootDir: bundledSkillsPath,
386
- virtualMode: true
387
- });
386
+ if (existsSync(workspaceSkillsPath)) {
387
+ const workspaceActivation = await resolveSkillActivation(workspaceSkillsPath);
388
+ if (workspaceActivation.inactiveSkills.length > 0) {
389
+ const summary = workspaceActivation.inactiveSkills.map((entry)=>`${entry.name} (missing: ${entry.missingBins.join(", ")})`).join("; ");
390
+ this.logger.info(`Inactive workspace skills: ${summary}`);
391
+ }
392
+ let workspaceOverlayRoot = workspaceSkillsPath;
393
+ try {
394
+ const workspaceOverlay = await createSkillOverlayDirectory(workspaceSkillsPath, workspaceActivation.activeSkillNames);
395
+ workspaceOverlayRoot = workspaceOverlay;
396
+ skillOverlayDirectories.push(workspaceOverlay);
397
+ } catch (error) {
398
+ this.logger.debug("Failed to build workspace skill overlay; falling back to unfiltered skills directory", error);
399
+ }
400
+ backendOverrides[skillsVirtualPath] = new FilesystemBackend({
401
+ rootDir: workspaceOverlayRoot,
402
+ virtualMode: true
403
+ });
404
+ skillsSources.push(skillsVirtualPath);
405
+ }
406
+ if (existsSync(bundledSkillsPath)) {
407
+ const bundledActivation = await resolveSkillActivation(bundledSkillsPath);
408
+ if (bundledActivation.inactiveSkills.length > 0) {
409
+ const summary = bundledActivation.inactiveSkills.map((entry)=>`${entry.name} (missing: ${entry.missingBins.join(", ")})`).join("; ");
410
+ this.logger.info(`Inactive bundled skills: ${summary}`);
411
+ }
412
+ let bundledOverlayRoot = bundledSkillsPath;
413
+ try {
414
+ const bundledOverlay = await createSkillOverlayDirectory(bundledSkillsPath, bundledActivation.activeSkillNames);
415
+ bundledOverlayRoot = bundledOverlay;
416
+ skillOverlayDirectories.push(bundledOverlay);
417
+ } catch (error) {
418
+ this.logger.debug("Failed to build bundled skill overlay; falling back to unfiltered skills directory", error);
419
+ }
420
+ backendOverrides["/skills-bundled/"] = new FilesystemBackend({
421
+ rootDir: bundledOverlayRoot,
422
+ virtualMode: true
423
+ });
424
+ skillsSources.push("/skills-bundled/");
425
+ }
388
426
  if (outputMount.virtualPath && outputMount.absolutePath) backendOverrides[outputMount.virtualPath] = new FilesystemBackend({
389
427
  rootDir: outputMount.absolutePath,
390
428
  virtualMode: true
@@ -522,6 +560,11 @@ class AgentInvoker {
522
560
  this.outputManager.emitAgentError(errorWithToolContext);
523
561
  throw error;
524
562
  } finally{
563
+ for (const overlayDirectory of skillOverlayDirectories)try {
564
+ await removeSkillOverlayDirectory(overlayDirectory);
565
+ } catch (error) {
566
+ this.logger.debug(`Failed to cleanup skill overlay directory ${overlayDirectory}`, error);
567
+ }
525
568
  if (this.mcpManager) {
526
569
  this.logger.debug("Cleaning up MCP client");
527
570
  await this.mcpManager.cleanup();
@@ -553,6 +596,10 @@ class AgentInvoker {
553
596
  _define_property(this, "workdir", null);
554
597
  _define_property(this, "defaultOutputDir", null);
555
598
  _define_property(this, "mcpProxyConfig", void 0);
599
+ _define_property(this, "nodeInvoker", void 0);
600
+ _define_property(this, "nodeDefaultTargetClientId", void 0);
601
+ _define_property(this, "nodeConnectedIdsProvider", void 0);
602
+ _define_property(this, "nodeConnectedTargetsProvider", void 0);
556
603
  this.outputManager = options.outputManager;
557
604
  this.logger = options.logger;
558
605
  this.workspace = options.workspace || process.cwd();
@@ -562,9 +609,18 @@ class AgentInvoker {
562
609
  this.workdir = options.workdir || null;
563
610
  this.defaultOutputDir = options.defaultOutputDir || null;
564
611
  this.mcpProxyConfig = options.mcpProxyConfig;
612
+ this.nodeInvoker = options.nodeInvoker;
613
+ this.nodeDefaultTargetClientId = options.nodeDefaultTargetClientId;
614
+ this.nodeConnectedIdsProvider = options.nodeConnectedIdsProvider;
615
+ this.nodeConnectedTargetsProvider = options.nodeConnectedTargetsProvider;
565
616
  const configLoader = new WingmanConfigLoader(this.configDir, this.workspace);
566
617
  this.wingmanConfig = configLoader.loadConfig();
567
- this.loader = new AgentLoader(this.configDir, this.workspace, this.wingmanConfig);
618
+ this.loader = new AgentLoader(this.configDir, this.workspace, this.wingmanConfig, this.workspace, {
619
+ terminalOwnerId: "default",
620
+ terminalSessionManager: this.terminalSessionManager,
621
+ nodeInvoker: this.nodeInvoker,
622
+ nodeDefaultTargetClientId: this.nodeDefaultTargetClientId
623
+ });
568
624
  }
569
625
  }
570
626
  function buildUserContent(prompt, attachments, model) {
@@ -27,6 +27,7 @@ __webpack_require__.d(__webpack_exports__, {
27
27
  SkillRepository: ()=>SkillRepository
28
28
  });
29
29
  const external_logger_cjs_namespaceObject = require("../../logger.cjs");
30
+ const metadata_cjs_namespaceObject = require("../../skills/metadata.cjs");
30
31
  function _define_property(obj, key, value) {
31
32
  if (key in obj) Object.defineProperty(obj, key, {
32
33
  value: value,
@@ -63,7 +64,8 @@ class SkillRepository {
63
64
  async listAvailableSkills() {
64
65
  try {
65
66
  if ("clawhub" === this.provider) return await this.listSkillsFromClawhub();
66
- return await this.listSkillsFromGitHub();
67
+ if ("github" === this.provider) return await this.listSkillsFromGitHub();
68
+ return await this.listSkillsFromHybrid();
67
69
  } catch (error) {
68
70
  if (error instanceof Error) throw new Error(`Failed to list skills: ${error.message}`);
69
71
  throw error;
@@ -85,19 +87,39 @@ class SkillRepository {
85
87
  return Buffer.from(arrayBuffer);
86
88
  }
87
89
  async getSkillMetadata(skillName) {
88
- if ("clawhub" === this.provider) return await this.getClawhubSkillMetadata(skillName);
89
90
  try {
90
- const skillMdPath = `/repos/${this.owner}/${this.repo}/contents/skills/${skillName}/SKILL.md`;
91
- const skillMd = await this.fetchGitHub(skillMdPath);
92
- if ("file" !== skillMd.type || !skillMd.content) throw new Error("SKILL.md not found or invalid");
93
- const content = Buffer.from(skillMd.content, "base64").toString("utf-8");
94
- const metadata = this.parseSkillMetadata(content);
95
- return metadata;
91
+ if ("clawhub" === this.provider) return await this.getClawhubSkillMetadata(skillName);
92
+ if ("github" === this.provider) {
93
+ const repository = await this.resolveGitHubRepositoryForSkill(skillName);
94
+ return await this.getGitHubSkillMetadata(skillName, repository);
95
+ }
96
+ return await this.getHybridSkillMetadata(skillName);
96
97
  } catch (error) {
97
98
  if (error instanceof Error) throw new Error(`Failed to fetch skill metadata for ${skillName}: ${error.message}`);
98
99
  throw error;
99
100
  }
100
101
  }
102
+ async getGitHubSkillMetadata(skillName, repository) {
103
+ const skillMdPath = `/repos/${repository.owner}/${repository.name}/contents/skills/${skillName}/SKILL.md`;
104
+ const skillMd = await this.fetchGitHub(skillMdPath);
105
+ if ("file" !== skillMd.type || !skillMd.content) throw new Error(`SKILL.md not found or invalid in ${repository.owner}/${repository.name}`);
106
+ const content = Buffer.from(skillMd.content, "base64").toString("utf-8");
107
+ return this.parseSkillMetadata(content);
108
+ }
109
+ async resolveGitHubRepositoryForSkill(skillName) {
110
+ const repositories = this.getGitHubRepositories();
111
+ for(let index = repositories.length - 1; index >= 0; index -= 1){
112
+ const repository = repositories[index];
113
+ try {
114
+ await this.fetchGitHub(`/repos/${repository.owner}/${repository.name}/contents/skills/${skillName}/SKILL.md`);
115
+ return repository;
116
+ } catch (error) {
117
+ if (error instanceof Error && error.message.includes("Resource not found")) continue;
118
+ throw error;
119
+ }
120
+ }
121
+ throw new Error(`Skill '${skillName}' not found in configured GitHub repositories: ${repositories.map((repository)=>`${repository.owner}/${repository.name}`).join(", ")}`);
122
+ }
101
123
  async getClawhubSkillMetadata(skillName) {
102
124
  try {
103
125
  const detail = await this.fetchJson(`${this.clawhubBaseUrl}/api/v1/skills/${encodeURIComponent(skillName)}`, {
@@ -127,51 +149,50 @@ class SkillRepository {
127
149
  throw error;
128
150
  }
129
151
  }
152
+ async getHybridSkillMetadata(skillName) {
153
+ let githubError;
154
+ try {
155
+ const repository = await this.resolveGitHubRepositoryForSkill(skillName);
156
+ return await this.getGitHubSkillMetadata(skillName, repository);
157
+ } catch (error) {
158
+ githubError = error;
159
+ logger.debug(`Falling back to ClawHub metadata lookup for '${skillName}' after GitHub error: ${error instanceof Error ? error.message : String(error)}`);
160
+ }
161
+ try {
162
+ return await this.getClawhubSkillMetadata(skillName);
163
+ } catch (clawhubError) {
164
+ throw new Error(`GitHub error: ${githubError instanceof Error ? githubError.message : String(githubError)}; ClawHub error: ${clawhubError instanceof Error ? clawhubError.message : String(clawhubError)}`);
165
+ }
166
+ }
130
167
  parseSkillMetadata(content) {
131
- const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---/;
132
- const match = content.match(frontmatterRegex);
133
- if (!match) throw new Error("Invalid SKILL.md format: missing YAML frontmatter");
134
- const frontmatter = match[1];
135
- const metadata = {
136
- name: "",
137
- description: ""
168
+ const parsed = (0, metadata_cjs_namespaceObject.parseSkillFrontmatter)(content);
169
+ return {
170
+ name: parsed.name,
171
+ description: parsed.description,
172
+ ...parsed.license ? {
173
+ license: parsed.license
174
+ } : {},
175
+ ...parsed.compatibility ? {
176
+ compatibility: parsed.compatibility
177
+ } : {},
178
+ ...parsed.allowedTools.length > 0 ? {
179
+ allowedTools: parsed.allowedTools
180
+ } : {},
181
+ ...parsed.metadata ? {
182
+ metadata: parsed.metadata
183
+ } : {}
138
184
  };
139
- const lines = frontmatter.split("\n");
140
- for (const line of lines){
141
- const colonIndex = line.indexOf(":");
142
- if (-1 === colonIndex) continue;
143
- const key = line.substring(0, colonIndex).trim();
144
- const value = line.substring(colonIndex + 1).trim();
145
- switch(key){
146
- case "name":
147
- metadata.name = value;
148
- break;
149
- case "description":
150
- metadata.description = value;
151
- break;
152
- case "license":
153
- metadata.license = value;
154
- break;
155
- case "compatibility":
156
- metadata.compatibility = value;
157
- break;
158
- case "allowed-tools":
159
- metadata.allowedTools = value;
160
- break;
161
- }
162
- }
163
- if (!metadata.name) throw new Error("Invalid SKILL.md: missing required field 'name'");
164
- if (!metadata.description) throw new Error("Invalid SKILL.md: missing required field 'description'");
165
- const nameRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/;
166
- if (!nameRegex.test(metadata.name)) throw new Error(`Invalid skill name '${metadata.name}': must be lowercase alphanumeric with hyphens only`);
167
- return metadata;
168
185
  }
169
186
  async downloadSkill(skillName) {
170
- if ("clawhub" === this.provider) return await this.downloadSkillFromClawhub(skillName);
171
187
  try {
172
- const files = new Map();
173
- await this.downloadDirectory(`skills/${skillName}`, files, skillName);
174
- return files;
188
+ if ("clawhub" === this.provider) return await this.downloadSkillFromClawhub(skillName);
189
+ if ("github" === this.provider) {
190
+ const repository = await this.resolveGitHubRepositoryForSkill(skillName);
191
+ const files = new Map();
192
+ await this.downloadDirectory(`skills/${skillName}`, files, skillName, repository);
193
+ return files;
194
+ }
195
+ return await this.downloadHybridSkill(skillName);
175
196
  } catch (error) {
176
197
  if (error instanceof Error) throw new Error(`Failed to download skill ${skillName}: ${error.message}`);
177
198
  throw error;
@@ -214,8 +235,25 @@ class SkillRepository {
214
235
  throw error;
215
236
  }
216
237
  }
217
- async downloadDirectory(path, files, skillName) {
218
- const contents = await this.fetchGitHub(`/repos/${this.owner}/${this.repo}/contents/${path}`);
238
+ async downloadHybridSkill(skillName) {
239
+ let githubError;
240
+ try {
241
+ const repository = await this.resolveGitHubRepositoryForSkill(skillName);
242
+ const files = new Map();
243
+ await this.downloadDirectory(`skills/${skillName}`, files, skillName, repository);
244
+ return files;
245
+ } catch (error) {
246
+ githubError = error;
247
+ logger.debug(`Falling back to ClawHub download for '${skillName}' after GitHub error: ${error instanceof Error ? error.message : String(error)}`);
248
+ }
249
+ try {
250
+ return await this.downloadSkillFromClawhub(skillName);
251
+ } catch (clawhubError) {
252
+ throw new Error(`GitHub error: ${githubError instanceof Error ? githubError.message : String(githubError)}; ClawHub error: ${clawhubError instanceof Error ? clawhubError.message : String(clawhubError)}`);
253
+ }
254
+ }
255
+ async downloadDirectory(path, files, skillName, repository) {
256
+ const contents = await this.fetchGitHub(`/repos/${repository.owner}/${repository.name}/contents/${path}`);
219
257
  for (const item of contents)if ("file" === item.type) if (item.content) {
220
258
  const content = Buffer.from(item.content, "base64");
221
259
  const relativePath = item.path.replace(`skills/${skillName}/`, "");
@@ -228,7 +266,7 @@ class SkillRepository {
228
266
  files.set(relativePath, content);
229
267
  }
230
268
  }
231
- else if ("dir" === item.type) await this.downloadDirectory(item.path, files, skillName);
269
+ else if ("dir" === item.type) await this.downloadDirectory(item.path, files, skillName, repository);
232
270
  }
233
271
  async listSkillsFromClawhub() {
234
272
  const allSkills = [];
@@ -257,32 +295,80 @@ class SkillRepository {
257
295
  }while (cursor);
258
296
  return allSkills;
259
297
  }
260
- async listSkillsFromGitHub() {
261
- const contents = await this.fetchGitHub(`/repos/${this.owner}/${this.repo}/contents/skills`);
262
- const skills = [];
263
- for (const item of contents)if ("dir" === item.type) try {
264
- const metadata = await this.getSkillMetadata(item.name);
265
- skills.push({
266
- name: item.name,
267
- description: metadata.description || "No description",
268
- path: item.path,
269
- metadata
270
- });
298
+ async listSkillsFromHybrid() {
299
+ let clawhubSkills = [];
300
+ let githubSkills = [];
301
+ let clawhubError;
302
+ let githubError;
303
+ try {
304
+ clawhubSkills = await this.listSkillsFromClawhub();
271
305
  } catch (error) {
272
- logger.warn(`Could not read skill ${item.name}`, error instanceof Error ? error.message : String(error));
306
+ clawhubError = error;
307
+ logger.warn(`Failed to list ClawHub skills in hybrid mode: ${error instanceof Error ? error.message : String(error)}`);
273
308
  }
274
- return skills;
309
+ try {
310
+ githubSkills = await this.listSkillsFromGitHub();
311
+ } catch (error) {
312
+ githubError = error;
313
+ logger.warn(`Failed to list GitHub skills in hybrid mode: ${error instanceof Error ? error.message : String(error)}`);
314
+ }
315
+ if (0 === clawhubSkills.length && 0 === githubSkills.length) throw new Error(`No skill sources available. ClawHub error: ${clawhubError instanceof Error ? clawhubError.message : "none"}; GitHub error: ${githubError instanceof Error ? githubError.message : "none"}`);
316
+ const mergedSkills = new Map();
317
+ for (const skill of clawhubSkills)mergedSkills.set(skill.name, skill);
318
+ for (const skill of githubSkills){
319
+ if (mergedSkills.has(skill.name)) mergedSkills.delete(skill.name);
320
+ mergedSkills.set(skill.name, skill);
321
+ }
322
+ return [
323
+ ...mergedSkills.values()
324
+ ];
325
+ }
326
+ async listSkillsFromGitHub() {
327
+ const mergedSkills = new Map();
328
+ for (const repository of this.getGitHubRepositories()){
329
+ const contents = await this.fetchGitHub(`/repos/${repository.owner}/${repository.name}/contents/skills`);
330
+ for (const item of contents)if ("dir" === item.type) try {
331
+ const metadata = await this.getGitHubSkillMetadata(item.name, repository);
332
+ if (mergedSkills.has(item.name)) mergedSkills.delete(item.name);
333
+ mergedSkills.set(item.name, {
334
+ name: item.name,
335
+ description: metadata.description || "No description",
336
+ path: item.path,
337
+ metadata
338
+ });
339
+ } catch (error) {
340
+ logger.warn(`Could not read skill ${item.name} from ${repository.owner}/${repository.name}`, error instanceof Error ? error.message : String(error));
341
+ }
342
+ }
343
+ return [
344
+ ...mergedSkills.values()
345
+ ];
346
+ }
347
+ getGitHubRepositories() {
348
+ if (this.repositories.length > 0) return this.repositories;
349
+ throw new Error("No GitHub skill repositories configured. Set skills.repositories or the legacy skills.repositoryOwner + skills.repositoryName fields.");
275
350
  }
276
351
  constructor(options = {}){
277
352
  _define_property(this, "githubBaseUrl", "https://api.github.com");
278
- _define_property(this, "owner", void 0);
279
- _define_property(this, "repo", void 0);
353
+ _define_property(this, "repositories", void 0);
280
354
  _define_property(this, "token", void 0);
281
355
  _define_property(this, "provider", void 0);
282
356
  _define_property(this, "clawhubBaseUrl", void 0);
283
- this.provider = options.provider || "github";
284
- this.owner = options.repositoryOwner || "anthropics";
285
- this.repo = options.repositoryName || "skills";
357
+ this.provider = options.provider || "hybrid";
358
+ const normalizedRepositories = (options.repositories || []).map((repository)=>({
359
+ owner: repository.owner.trim(),
360
+ name: repository.name.trim()
361
+ })).filter((repository)=>repository.owner && repository.name);
362
+ const legacyOwner = options.repositoryOwner?.trim();
363
+ const legacyName = options.repositoryName?.trim();
364
+ if (normalizedRepositories.length > 0) this.repositories = normalizedRepositories;
365
+ else if (legacyOwner && legacyName) this.repositories = [
366
+ {
367
+ owner: legacyOwner,
368
+ name: legacyName
369
+ }
370
+ ];
371
+ else this.repositories = [];
286
372
  this.token = options.githubToken || process.env.GITHUB_TOKEN || void 0;
287
373
  this.clawhubBaseUrl = (options.clawhubBaseUrl || "https://clawhub.ai").replace(/\/+$/, "");
288
374
  }
@@ -4,8 +4,7 @@ import type { SkillInfo, SkillMetadata, SkillRepositoryOptions } from "../types/
4
4
  */
5
5
  export declare class SkillRepository {
6
6
  private readonly githubBaseUrl;
7
- private readonly owner;
8
- private readonly repo;
7
+ private readonly repositories;
9
8
  private readonly token?;
10
9
  private readonly provider;
11
10
  private readonly clawhubBaseUrl;
@@ -24,7 +23,10 @@ export declare class SkillRepository {
24
23
  * Get skill metadata by fetching and parsing SKILL.md
25
24
  */
26
25
  getSkillMetadata(skillName: string): Promise<SkillMetadata>;
26
+ private getGitHubSkillMetadata;
27
+ private resolveGitHubRepositoryForSkill;
27
28
  private getClawhubSkillMetadata;
29
+ private getHybridSkillMetadata;
28
30
  /**
29
31
  * Parse SKILL.md content to extract YAML frontmatter
30
32
  */
@@ -34,10 +36,13 @@ export declare class SkillRepository {
34
36
  */
35
37
  downloadSkill(skillName: string): Promise<Map<string, string | Buffer>>;
36
38
  private downloadSkillFromClawhub;
39
+ private downloadHybridSkill;
37
40
  /**
38
41
  * Recursively download all files in a directory
39
42
  */
40
43
  private downloadDirectory;
41
44
  private listSkillsFromClawhub;
45
+ private listSkillsFromHybrid;
42
46
  private listSkillsFromGitHub;
47
+ private getGitHubRepositories;
43
48
  }