@matimo/core 0.1.0-alpha.12.1 → 0.1.0-alpha.14

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 (118) hide show
  1. package/README.md +169 -8
  2. package/dist/approval/approval-handler.d.ts +5 -1
  3. package/dist/approval/approval-handler.d.ts.map +1 -1
  4. package/dist/approval/approval-handler.js +6 -0
  5. package/dist/approval/approval-handler.js.map +1 -1
  6. package/dist/core/schema.d.ts +29 -8
  7. package/dist/core/schema.d.ts.map +1 -1
  8. package/dist/core/schema.js +10 -3
  9. package/dist/core/schema.js.map +1 -1
  10. package/dist/core/skill-content-parser.d.ts +91 -0
  11. package/dist/core/skill-content-parser.d.ts.map +1 -0
  12. package/dist/core/skill-content-parser.js +248 -0
  13. package/dist/core/skill-content-parser.js.map +1 -0
  14. package/dist/core/skill-loader.d.ts +46 -0
  15. package/dist/core/skill-loader.d.ts.map +1 -0
  16. package/dist/core/skill-loader.js +310 -0
  17. package/dist/core/skill-loader.js.map +1 -0
  18. package/dist/core/skill-registry.d.ts +131 -0
  19. package/dist/core/skill-registry.d.ts.map +1 -0
  20. package/dist/core/skill-registry.js +316 -0
  21. package/dist/core/skill-registry.js.map +1 -0
  22. package/dist/core/tfidf-embedding.d.ts +45 -0
  23. package/dist/core/tfidf-embedding.d.ts.map +1 -0
  24. package/dist/core/tfidf-embedding.js +199 -0
  25. package/dist/core/tfidf-embedding.js.map +1 -0
  26. package/dist/core/types.d.ts +155 -6
  27. package/dist/core/types.d.ts.map +1 -1
  28. package/dist/errors/matimo-error.d.ts +3 -1
  29. package/dist/errors/matimo-error.d.ts.map +1 -1
  30. package/dist/errors/matimo-error.js +2 -0
  31. package/dist/errors/matimo-error.js.map +1 -1
  32. package/dist/executors/command-executor.d.ts.map +1 -1
  33. package/dist/executors/command-executor.js +13 -2
  34. package/dist/executors/command-executor.js.map +1 -1
  35. package/dist/executors/function-executor.d.ts.map +1 -1
  36. package/dist/executors/function-executor.js +33 -20
  37. package/dist/executors/function-executor.js.map +1 -1
  38. package/dist/index.d.ts +20 -3
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +14 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/integrations/langchain.d.ts +55 -0
  43. package/dist/integrations/langchain.d.ts.map +1 -1
  44. package/dist/integrations/langchain.js +66 -0
  45. package/dist/integrations/langchain.js.map +1 -1
  46. package/dist/logging/winston-logger.d.ts.map +1 -1
  47. package/dist/logging/winston-logger.js +9 -1
  48. package/dist/logging/winston-logger.js.map +1 -1
  49. package/dist/matimo-instance.d.ts +171 -6
  50. package/dist/matimo-instance.d.ts.map +1 -1
  51. package/dist/matimo-instance.js +602 -13
  52. package/dist/matimo-instance.js.map +1 -1
  53. package/dist/mcp/mcp-server.d.ts +25 -0
  54. package/dist/mcp/mcp-server.d.ts.map +1 -1
  55. package/dist/mcp/mcp-server.js +128 -21
  56. package/dist/mcp/mcp-server.js.map +1 -1
  57. package/dist/mcp/tool-converter.d.ts.map +1 -1
  58. package/dist/mcp/tool-converter.js +10 -1
  59. package/dist/mcp/tool-converter.js.map +1 -1
  60. package/dist/policy/approval-manifest.d.ts +74 -0
  61. package/dist/policy/approval-manifest.d.ts.map +1 -0
  62. package/dist/policy/approval-manifest.js +183 -0
  63. package/dist/policy/approval-manifest.js.map +1 -0
  64. package/dist/policy/content-validator.d.ts +19 -0
  65. package/dist/policy/content-validator.d.ts.map +1 -0
  66. package/dist/policy/content-validator.js +196 -0
  67. package/dist/policy/content-validator.js.map +1 -0
  68. package/dist/policy/default-policy.d.ts +46 -0
  69. package/dist/policy/default-policy.d.ts.map +1 -0
  70. package/dist/policy/default-policy.js +241 -0
  71. package/dist/policy/default-policy.js.map +1 -0
  72. package/dist/policy/events.d.ts +71 -0
  73. package/dist/policy/events.d.ts.map +1 -0
  74. package/dist/policy/events.js +8 -0
  75. package/dist/policy/events.js.map +1 -0
  76. package/dist/policy/index.d.ts +13 -0
  77. package/dist/policy/index.d.ts.map +1 -0
  78. package/dist/policy/index.js +9 -0
  79. package/dist/policy/index.js.map +1 -0
  80. package/dist/policy/integrity-tracker.d.ts +62 -0
  81. package/dist/policy/integrity-tracker.d.ts.map +1 -0
  82. package/dist/policy/integrity-tracker.js +79 -0
  83. package/dist/policy/integrity-tracker.js.map +1 -0
  84. package/dist/policy/policy-loader.d.ts +58 -0
  85. package/dist/policy/policy-loader.d.ts.map +1 -0
  86. package/dist/policy/policy-loader.js +153 -0
  87. package/dist/policy/policy-loader.js.map +1 -0
  88. package/dist/policy/risk-classifier.d.ts +18 -0
  89. package/dist/policy/risk-classifier.d.ts.map +1 -0
  90. package/dist/policy/risk-classifier.js +43 -0
  91. package/dist/policy/risk-classifier.js.map +1 -0
  92. package/dist/policy/types.d.ts +126 -0
  93. package/dist/policy/types.d.ts.map +1 -0
  94. package/dist/policy/types.js +8 -0
  95. package/dist/policy/types.js.map +1 -0
  96. package/package.json +5 -5
  97. package/tools/matimo_approve_tool/definition.yaml +36 -0
  98. package/tools/matimo_approve_tool/matimo_approve_tool.ts +90 -0
  99. package/tools/matimo_create_skill/definition.yaml +46 -0
  100. package/tools/matimo_create_skill/matimo_create_skill.ts +75 -0
  101. package/tools/matimo_create_tool/definition.yaml +48 -0
  102. package/tools/matimo_create_tool/matimo_create_tool.ts +137 -0
  103. package/tools/matimo_get_skill/definition.yaml +60 -0
  104. package/tools/matimo_get_skill/matimo_get_skill.ts +182 -0
  105. package/tools/matimo_get_tool_status/definition.yaml +42 -0
  106. package/tools/matimo_get_tool_status/matimo_get_tool_status.ts +101 -0
  107. package/tools/matimo_list_skills/definition.yaml +52 -0
  108. package/tools/matimo_list_skills/matimo_list_skills.ts +138 -0
  109. package/tools/matimo_list_user_tools/definition.yaml +32 -0
  110. package/tools/matimo_list_user_tools/matimo_list_user_tools.ts +74 -0
  111. package/tools/matimo_reload_tools/definition.yaml +35 -0
  112. package/tools/matimo_reload_tools/matimo_reload_tools.ts +29 -0
  113. package/tools/matimo_validate_skill/definition.yaml +43 -0
  114. package/tools/matimo_validate_skill/matimo_validate_skill.ts +137 -0
  115. package/tools/matimo_validate_tool/definition.yaml +34 -0
  116. package/tools/matimo_validate_tool/matimo_validate_tool.ts +168 -0
  117. package/tools/shared/skill-validation.ts +335 -0
  118. package/LICENSE +0 -21
@@ -1,28 +1,102 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _MatimoInstance_instances, _MatimoInstance_policy, _MatimoInstance_integrityTracker, _MatimoInstance_approvalManifest, _MatimoInstance_onEvent, _MatimoInstance_hitlCallback, _MatimoInstance_trustedPaths, _MatimoInstance_untrustedPaths, _MatimoInstance_policyFile, _MatimoInstance_emitEvent, _MatimoInstance_resolveHITL;
13
+ import fs from 'fs';
1
14
  import path from 'path';
2
15
  import { ToolLoader } from './core/tool-loader';
3
16
  import { ToolRegistry } from './core/tool-registry';
17
+ import { SkillLoader } from './core/skill-loader';
18
+ import { SkillRegistry } from './core/skill-registry';
4
19
  import { CommandExecutor } from './executors/command-executor';
5
20
  import { HttpExecutor } from './executors/http-executor';
6
21
  import { FunctionExecutor } from './executors/function-executor';
7
22
  import { MatimoError, ErrorCode } from './errors/matimo-error';
8
23
  import { getLoggerConfig, createLogger, setGlobalMatimoLogger, } from './logging';
9
24
  import { getGlobalApprovalHandler } from './approval/approval-handler';
25
+ import { DefaultPolicyEngine } from './policy/default-policy';
26
+ import { loadPolicyFromFile } from './policy/policy-loader';
27
+ import { ToolIntegrityTracker } from './policy/integrity-tracker';
28
+ import { ApprovalManifest } from './policy/approval-manifest';
29
+ /**
30
+ * Find the core skills directory by walking up from process.cwd().
31
+ * Works in CJS (Jest), ESM (tsx), compiled dist, and on all platforms.
32
+ */
33
+ function findCoreSkillsPath() {
34
+ let dir = process.cwd();
35
+ for (let i = 0; i < 20; i++) {
36
+ const candidate = path.join(dir, 'packages', 'core', 'skills');
37
+ if (fs.existsSync(candidate))
38
+ return candidate;
39
+ // Also check node_modules/@matimo/core/skills
40
+ const nmCandidate = path.join(dir, 'node_modules', '@matimo', 'core', 'skills');
41
+ if (fs.existsSync(nmCandidate))
42
+ return nmCandidate;
43
+ const parent = path.dirname(dir);
44
+ if (parent === dir)
45
+ break;
46
+ dir = parent;
47
+ }
48
+ return null;
49
+ }
10
50
  /**
11
51
  * Matimo Instance - Single initialization point for tool execution
12
52
  * Combines loader, registry, and executors into one interface
13
53
  */
14
54
  export class MatimoInstance {
15
- constructor(toolPaths, logger) {
55
+ constructor(toolPaths, skillPaths, logger, policyOptions) {
56
+ _MatimoInstance_instances.add(this);
57
+ // Policy engine fields — runtime-enforced encapsulation via ES #private
58
+ _MatimoInstance_policy.set(this, void 0);
59
+ _MatimoInstance_integrityTracker.set(this, void 0);
60
+ _MatimoInstance_approvalManifest.set(this, void 0);
61
+ _MatimoInstance_onEvent.set(this, void 0);
62
+ _MatimoInstance_hitlCallback.set(this, void 0);
63
+ _MatimoInstance_trustedPaths.set(this, void 0);
64
+ _MatimoInstance_untrustedPaths.set(this, void 0);
65
+ _MatimoInstance_policyFile.set(this, void 0);
16
66
  this.toolPaths = toolPaths;
67
+ this.skillPaths = skillPaths;
17
68
  this.logger = logger;
18
69
  this.loader = new ToolLoader();
19
70
  this.registry = new ToolRegistry();
71
+ this.skillLoader = new SkillLoader();
72
+ this.skillRegistry = new SkillRegistry();
20
73
  // Use the first path (primary) as working directory for command executor
21
74
  const workingDir = toolPaths.length > 0 ? path.dirname(toolPaths[0]) : process.cwd();
22
75
  this.commandExecutor = new CommandExecutor(workingDir);
23
76
  this.httpExecutor = new HttpExecutor();
24
77
  this.functionExecutor = new FunctionExecutor(toolPaths[0] || '');
25
78
  this.approvalHandler = getGlobalApprovalHandler();
79
+ // Policy engine setup
80
+ __classPrivateFieldSet(this, _MatimoInstance_policy, policyOptions?.policy ?? null, "f");
81
+ __classPrivateFieldSet(this, _MatimoInstance_trustedPaths, policyOptions?.trustedPaths ?? [], "f");
82
+ __classPrivateFieldSet(this, _MatimoInstance_untrustedPaths, policyOptions?.untrustedPaths ?? [], "f");
83
+ __classPrivateFieldSet(this, _MatimoInstance_integrityTracker, new ToolIntegrityTracker(), "f");
84
+ __classPrivateFieldSet(this, _MatimoInstance_onEvent, policyOptions?.onEvent ?? null, "f");
85
+ __classPrivateFieldSet(this, _MatimoInstance_hitlCallback, policyOptions?.onHITL ?? null, "f");
86
+ __classPrivateFieldSet(this, _MatimoInstance_policyFile, policyOptions?.policyFile ?? null, "f");
87
+ // Approval manifest
88
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
89
+ const approvalDir = policyOptions?.approvalDir ?? process.cwd();
90
+ __classPrivateFieldSet(this, _MatimoInstance_approvalManifest, new ApprovalManifest(approvalDir, policyOptions?.approvalSecret), "f");
91
+ }
92
+ else {
93
+ __classPrivateFieldSet(this, _MatimoInstance_approvalManifest, null, "f");
94
+ }
95
+ // Freeze policy to prevent runtime mutation by agents.
96
+ // (The SDK's own reloadPolicy() bypasses the frozen reference by replacing #policy.)
97
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
98
+ Object.freeze(__classPrivateFieldGet(this, _MatimoInstance_policy, "f"));
99
+ }
26
100
  }
27
101
  /**
28
102
  * Initialize Matimo with tools from directory or auto-discovery
@@ -82,27 +156,83 @@ export class MatimoInstance {
82
156
  autoDiscover: finalOptions.autoDiscover,
83
157
  });
84
158
  const toolPaths = [];
159
+ const skillPaths = [];
85
160
  // Include core tools (calculator, etc.) - currently not used in monorepo
86
161
  // Use explicit toolPaths or autoDiscover instead
87
162
  // if (finalOptions.includeCore) { ... }
88
- // Add explicit paths
163
+ // Add explicit tool paths
89
164
  if (finalOptions.toolPaths) {
90
165
  toolPaths.push(...finalOptions.toolPaths);
91
166
  logger.debug(`Adding explicit tool paths`, { count: finalOptions.toolPaths.length });
92
167
  }
168
+ // Add explicit skill paths (include core skills by default)
169
+ if (finalOptions.skillPaths) {
170
+ skillPaths.push(...finalOptions.skillPaths);
171
+ logger.debug(`Adding explicit skill paths`, { count: finalOptions.skillPaths.length });
172
+ }
173
+ // Always include core skills bundled with Matimo
174
+ const coreSkillsPath = findCoreSkillsPath();
175
+ if (coreSkillsPath && !skillPaths.includes(coreSkillsPath)) {
176
+ skillPaths.push(coreSkillsPath);
177
+ logger.debug(`Including core skills`, { path: coreSkillsPath });
178
+ }
93
179
  // Auto-discover @matimo/* packages
94
180
  if (finalOptions.autoDiscover) {
95
181
  const discoveredPaths = new ToolLoader().autoDiscoverPackages();
96
182
  toolPaths.push(...discoveredPaths);
97
183
  logger.debug(`Auto-discovered tool paths`, { count: discoveredPaths.length });
184
+ // Also discover skill paths from the same @matimo/* packages
185
+ // Each discovered tool path is like @matimo/slack/tools/ — sibling skills/ may exist
186
+ for (const toolPath of discoveredPaths) {
187
+ const pkgDir = path.dirname(toolPath); // e.g. @matimo/slack/
188
+ const pkgSkillsPath = path.join(pkgDir, 'skills');
189
+ if (fs.existsSync(pkgSkillsPath) && !skillPaths.includes(pkgSkillsPath)) {
190
+ skillPaths.push(pkgSkillsPath);
191
+ }
192
+ }
193
+ logger.debug(`Auto-discovered skill paths`, { count: skillPaths.length });
194
+ }
195
+ // Build policy engine
196
+ let policy = null;
197
+ if (finalOptions.policy) {
198
+ policy = finalOptions.policy;
98
199
  }
99
- const instance = new MatimoInstance(toolPaths, logger);
200
+ else if (finalOptions.policyFile) {
201
+ policy = loadPolicyFromFile(finalOptions.policyFile);
202
+ }
203
+ else if (finalOptions.policyConfig) {
204
+ policy = new DefaultPolicyEngine(finalOptions.policyConfig);
205
+ }
206
+ const instance = new MatimoInstance(toolPaths, skillPaths, logger, {
207
+ policy,
208
+ trustedPaths: finalOptions.trustedPaths,
209
+ untrustedPaths: finalOptions.untrustedPaths,
210
+ approvalSecret: finalOptions.approvalSecret,
211
+ approvalDir: finalOptions.approvalDir,
212
+ onEvent: finalOptions.onEvent,
213
+ onHITL: finalOptions.onHITL,
214
+ policyFile: finalOptions.policyFile,
215
+ });
100
216
  // Load tools from all paths
101
217
  const allTools = instance.loader.loadToolsFromMultiplePaths(toolPaths);
102
218
  instance.registry.registerAll(Array.from(allTools.values()));
219
+ // Load skills from all paths
220
+ for (const skillPath of skillPaths) {
221
+ try {
222
+ const skillsFromPath = instance.skillLoader.loadSkillsFromDirectory(skillPath, skillPath.includes('core') ? 'builtin' : 'user');
223
+ instance.skillRegistry.registerAll(skillsFromPath);
224
+ }
225
+ catch (err) {
226
+ logger.warn(`Failed to load skills from path: ${skillPath}`, {
227
+ error: err.message,
228
+ });
229
+ }
230
+ }
103
231
  logger.info(`Matimo SDK initialized successfully`, {
104
232
  toolCount: allTools.size,
233
+ skillCount: instance.skillRegistry.count(),
105
234
  paths: toolPaths.length,
235
+ skillPaths: skillPaths.length,
106
236
  });
107
237
  return instance;
108
238
  }
@@ -151,6 +281,38 @@ export class MatimoInstance {
151
281
  paramCount: Object.keys(params).length,
152
282
  });
153
283
  try {
284
+ // Policy check: enforce RBAC and tool status before any execution
285
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
286
+ const policyContext = options?.context ?? {};
287
+ const decision = __classPrivateFieldGet(this, _MatimoInstance_policy, "f").canExecute(policyContext, tool);
288
+ if (decision.allowed === false) {
289
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
290
+ type: 'tool:execution_denied',
291
+ toolName,
292
+ reason: decision.reason,
293
+ agentId: policyContext.agentId,
294
+ timestamp: new Date().toISOString(),
295
+ });
296
+ throw new MatimoError(`Policy denied execution of '${toolName}': ${decision.reason}`, ErrorCode.POLICY_DENIED, { toolName, reason: decision.reason, riskLevel: decision.riskLevel });
297
+ }
298
+ // Handle quarantined tools — check approval manifest or invoke HITL callback
299
+ if (decision.allowed === 'pending_approval') {
300
+ const approved = await __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_resolveHITL).call(this, tool, decision, policyContext);
301
+ if (!approved) {
302
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
303
+ type: 'tool:quarantine_rejected',
304
+ toolName,
305
+ timestamp: new Date().toISOString(),
306
+ });
307
+ throw new MatimoError(`Tool '${toolName}' is quarantined and was not approved: ${decision.reason}`, ErrorCode.POLICY_DENIED, { toolName, reason: decision.reason, riskLevel: decision.riskLevel });
308
+ }
309
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
310
+ type: 'tool:quarantine_approved',
311
+ toolName,
312
+ timestamp: new Date().toISOString(),
313
+ });
314
+ }
315
+ }
154
316
  // Simple approval flow:
155
317
  // 1. Check if tool requires approval (from YAML or keyword detection)
156
318
  // 2. Check if pre-approved via env vars
@@ -178,7 +340,12 @@ export class MatimoInstance {
178
340
  scanContent = parts.join(' ');
179
341
  }
180
342
  const requiresApproval = this.approvalHandler.requiresApproval(tool.requires_approval, scanContent);
181
- if (requiresApproval && !this.approvalHandler.isPreApproved(toolName)) {
343
+ // Skip approval prompt if options.approved=true (e.g., MCP pre-confirmed approval).
344
+ // This avoids mutating global state in concurrent scenarios.
345
+ const skipApprovalPrompt = options?.approved === true;
346
+ if (requiresApproval &&
347
+ !this.approvalHandler.isPreApproved(toolName) &&
348
+ !skipApprovalPrompt) {
182
349
  this.logger.debug(`Approval required for: ${toolName}`, { toolName });
183
350
  await this.approvalHandler.requestApproval({
184
351
  toolName,
@@ -192,8 +359,32 @@ export class MatimoInstance {
192
359
  // Auto-inject authentication parameters. When per-call credentials are
193
360
  // supplied they take precedence over process.env (multi-tenant support).
194
361
  const finalParams = this.injectAuthParameters(tool, params, credentials);
362
+ // After injection, detect any auth-looking placeholders that are still unfilled.
363
+ // This gives a clear actionable error instead of silently sending a bad header to the API.
364
+ this.assertAuthParamsFilled(tool, finalParams, credentials);
195
365
  // Apply per-call timeout override if provided. Create a shallow copy so the
196
366
  // registered tool definition is never mutated between calls.
367
+ // Built-in interception: matimo_reload_tools must run on the instance
368
+ // itself because reloadTools() clears/rebuilds the in-memory registry.
369
+ // The function executor has no reference to the MatimoInstance, so we
370
+ // handle it directly here. This works identically for SDK, LangChain,
371
+ // and MCP callers.
372
+ if (toolName === 'matimo_reload_tools') {
373
+ const reloadResult = await this.reloadTools();
374
+ this.logger.info('matimo_reload_tools: reload completed', {
375
+ loaded: reloadResult.loaded,
376
+ removed: reloadResult.removed,
377
+ rejected: reloadResult.rejected.length,
378
+ });
379
+ return {
380
+ success: true,
381
+ loaded: reloadResult.loaded,
382
+ removed: reloadResult.removed,
383
+ revalidated: reloadResult.revalidated,
384
+ rejected: reloadResult.rejected,
385
+ message: `Reload complete. ${reloadResult.loaded} tools loaded, ${reloadResult.removed} removed, ${reloadResult.rejected.length} rejected.`,
386
+ };
387
+ }
197
388
  const effectiveTool = timeoutOverride !== undefined
198
389
  ? { ...tool, execution: { ...tool.execution, timeout: timeoutOverride } }
199
390
  : tool;
@@ -222,34 +413,47 @@ export class MatimoInstance {
222
413
  return this.registry.get(toolName);
223
414
  }
224
415
  /**
225
- * List all available tools
416
+ * List all available tools, optionally filtered by policy.
417
+ * @param context - PolicyContext for filtering. If omitted and policy is active, returns all tools (backward compatible).
226
418
  * @returns Array of tool definitions
227
419
  */
228
- listTools() {
229
- return this.registry.getAll();
420
+ listTools(context) {
421
+ const tools = this.registry.getAll();
422
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && context) {
423
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f").filterForAgent(context, tools);
424
+ }
425
+ return tools;
230
426
  }
231
427
  /**
232
428
  * Get all available tools (alias for listTools)
233
429
  * @returns Array of tool definitions
234
430
  */
235
- getAllTools() {
236
- return this.registry.getAll();
431
+ getAllTools(context) {
432
+ return this.listTools(context);
237
433
  }
238
434
  /**
239
435
  * Search tools by name or description
240
436
  * @param query - Search query
241
437
  * @returns Matching tools
242
438
  */
243
- searchTools(query) {
244
- return this.registry.search(query);
439
+ searchTools(query, context) {
440
+ const results = this.registry.search(query);
441
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && context) {
442
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f").filterForAgent(context, results);
443
+ }
444
+ return results;
245
445
  }
246
446
  /**
247
447
  * Get tools by tag
248
448
  * @param tag - Tag to search for
249
449
  * @returns Tools with the given tag
250
450
  */
251
- getToolsByTag(tag) {
252
- return this.registry.getByTag(tag);
451
+ getToolsByTag(tag, context) {
452
+ const results = this.registry.getByTag(tag);
453
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && context) {
454
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f").filterForAgent(context, results);
455
+ }
456
+ return results;
253
457
  }
254
458
  /**
255
459
  * Return the credential key names that a tool expects.
@@ -311,6 +515,86 @@ export class MatimoInstance {
311
515
  }
312
516
  return credentialKeys;
313
517
  }
518
+ // ─── Skills API ──────────────────────────────────────────────────────────
519
+ /**
520
+ * List all available skills (Level 1 discovery - minimal context)
521
+ * @returns Array of skill summaries
522
+ */
523
+ listSkills() {
524
+ return this.skillRegistry.list();
525
+ }
526
+ /**
527
+ * Get a single skill by name (Level 2 activation - full content)
528
+ * @param name - Skill name
529
+ * @returns Skill definition or null
530
+ */
531
+ getSkill(name) {
532
+ return this.skillRegistry.get(name) || null;
533
+ }
534
+ /**
535
+ * Get selective skill content — only the sections an agent needs.
536
+ * Prevents dumping entire SKILL.md files into the LLM context window.
537
+ *
538
+ * @example
539
+ * // Get only error handling, max 500 tokens
540
+ * matimo.getSkillContent('postgres-query-operations', {
541
+ * sections: ['Error Handling'],
542
+ * maxTokens: 500,
543
+ * })
544
+ */
545
+ getSkillContent(name, options) {
546
+ return this.skillRegistry.getSkillContent(name, options);
547
+ }
548
+ /**
549
+ * List all sections of a skill with their token costs.
550
+ * Agents use this to decide which sections to load before activating.
551
+ */
552
+ getSkillSections(name) {
553
+ return this.skillRegistry.getSkillSections(name);
554
+ }
555
+ /**
556
+ * Search skills by keyword, category, difficulty, etc.
557
+ * Set `options.semantic = true` for embedding-based similarity ranking.
558
+ * @param options - Search options
559
+ * @returns Matching skills
560
+ */
561
+ searchSkills(options = {}) {
562
+ return this.skillRegistry.search(options);
563
+ }
564
+ /**
565
+ * Semantic search with relevance scores.
566
+ * Uses embeddings to find skills by meaning, not just keywords.
567
+ *
568
+ * @example
569
+ * const results = await matimo.semanticSearchSkills('How do I handle Postgres locking?');
570
+ * // → [{ skill: { name: 'postgres-query-operations' }, score: 0.82 }]
571
+ */
572
+ async semanticSearchSkills(query, options) {
573
+ return this.skillRegistry.semanticSearch(query, options);
574
+ }
575
+ /**
576
+ * Set a custom embedding provider for semantic skill search.
577
+ * If not set, a built-in TF-IDF provider is used.
578
+ */
579
+ setSkillEmbeddingProvider(provider) {
580
+ this.skillRegistry.setEmbeddingProvider(provider);
581
+ }
582
+ /**
583
+ * Get a bundled resource from a skill (Level 3 resources)
584
+ * @param skillName - Skill name
585
+ * @param resourcePath - Relative path to resource (e.g., "scripts/extract.py")
586
+ * @returns Resource content
587
+ */
588
+ getSkillResource(skillName, resourcePath) {
589
+ return this.skillLoader.loadSkillResource(skillName, this.skillPaths[0], resourcePath);
590
+ }
591
+ /**
592
+ * Get all skill paths
593
+ * @returns Array of skill paths
594
+ */
595
+ getSkillPaths() {
596
+ return [...this.skillPaths];
597
+ }
314
598
  /**
315
599
  * Automatically inject parameters from environment variables
316
600
  * Uses a YAML-native, scale-friendly approach:
@@ -369,6 +653,60 @@ export class MatimoInstance {
369
653
  }
370
654
  return result;
371
655
  }
656
+ /**
657
+ * After injectAuthParameters(), verify no auth-looking placeholders remain unfilled.
658
+ * Only checks HTTP headers (where auth credentials are injected) — not query params or body.
659
+ * Throws AUTH_FAILED with actionable guidance naming the missing env var(s).
660
+ */
661
+ assertAuthParamsFilled(tool, finalParams, credentials) {
662
+ const execution = tool.execution;
663
+ if (!('headers' in execution) || !execution.headers)
664
+ return;
665
+ const authPatterns = [
666
+ 'token',
667
+ 'key',
668
+ 'secret',
669
+ 'password',
670
+ 'credential',
671
+ 'auth',
672
+ 'bearer',
673
+ 'api_key',
674
+ ];
675
+ const placeholderRegex = /\{([^}]+)\}/g;
676
+ const missing = [];
677
+ // Only inspect headers — auth credentials belong there, not in query_params or body
678
+ for (const [headerName, headerValue] of Object.entries(execution.headers)) {
679
+ if (typeof headerValue !== 'string')
680
+ continue;
681
+ // Only check headers that look auth-related (Authorization, X-API-Key, etc.)
682
+ const lowerHeader = headerName.toLowerCase();
683
+ const isAuthHeader = lowerHeader === 'authorization' ||
684
+ lowerHeader.includes('auth') ||
685
+ lowerHeader.includes('token') ||
686
+ lowerHeader.includes('key') ||
687
+ lowerHeader.includes('secret');
688
+ if (!isAuthHeader)
689
+ continue;
690
+ let match;
691
+ placeholderRegex.lastIndex = 0;
692
+ while ((match = placeholderRegex.exec(headerValue)) !== null) {
693
+ const paramName = match[1];
694
+ if (paramName in finalParams)
695
+ continue;
696
+ const lowerName = paramName.toLowerCase();
697
+ if (authPatterns.some((p) => lowerName.includes(p))) {
698
+ missing.push(paramName);
699
+ }
700
+ }
701
+ }
702
+ if (missing.length === 0)
703
+ return;
704
+ const hints = missing
705
+ .map((n) => ` • ${n} → MATIMO_${n} (or pass via credentials option)`)
706
+ .join('\n');
707
+ const credentialsHint = credentials ? '' : ' (No per-call credentials were supplied.)';
708
+ throw new MatimoError(`Authentication credentials are missing for tool "${tool.name}".\n${hints}\n${credentialsHint}`.trim(), ErrorCode.AUTH_FAILED, { toolName: tool.name, missingCredentials: missing });
709
+ }
372
710
  /**
373
711
  * Extract all parameter placeholders from execution config
374
712
  * Scans headers, body, URL, and query_params for {paramName} patterns
@@ -470,7 +808,258 @@ export class MatimoInstance {
470
808
  throw new MatimoError(`Unsupported execution type: ${executionType}`, ErrorCode.EXECUTION_FAILED, { executionType });
471
809
  }
472
810
  }
811
+ /**
812
+ * Hot-reload tools from all configured paths.
813
+ * Re-validates untrusted tools via content validator and integrity tracker.
814
+ * Tools that fail validation are rejected and not loaded.
815
+ *
816
+ * Atomic: if loading fails mid-way (e.g. I/O error), the registry is restored
817
+ * to its previous state and `rolledBack: true` is included in the result.
818
+ */
819
+ async reloadTools() {
820
+ const previousNames = new Set(this.registry.getAll().map((t) => t.name));
821
+ // Snapshot the previous registry state for rollback on partial failure
822
+ const snapshot = this.registry.getAll();
823
+ this.registry.clear();
824
+ const result = {
825
+ loaded: 0,
826
+ removed: 0,
827
+ revalidated: 0,
828
+ rejected: [],
829
+ rolledBack: false,
830
+ };
831
+ let allTools;
832
+ try {
833
+ allTools = this.loader.loadToolsFromMultiplePaths(this.toolPaths);
834
+ }
835
+ catch (err) {
836
+ // I/O failure during load — restore snapshot and signal rollback
837
+ this.registry.registerAll(snapshot);
838
+ result.rolledBack = true;
839
+ this.logger.error('reloadTools: failed to load tools, rolled back to previous state', {
840
+ error: err.message,
841
+ });
842
+ return result;
843
+ }
844
+ const untrustedSet = new Set(__classPrivateFieldGet(this, _MatimoInstance_untrustedPaths, "f"));
845
+ for (const [, tool] of allTools) {
846
+ const defPath = tool._definitionPath ?? '';
847
+ const isUntrusted = untrustedSet.size > 0 && __classPrivateFieldGet(this, _MatimoInstance_untrustedPaths, "f").some((up) => defPath.startsWith(up));
848
+ if (isUntrusted && __classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
849
+ // Run policy validation on untrusted tools
850
+ const policyDecision = __classPrivateFieldGet(this, _MatimoInstance_policy, "f").canCreate({}, tool);
851
+ if (policyDecision.allowed === false) {
852
+ result.rejected.push(tool.name);
853
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
854
+ type: 'tool:rejected',
855
+ toolName: tool.name,
856
+ violations: [
857
+ { rule: 'policy-denied', severity: 'high', message: policyDecision.reason },
858
+ ],
859
+ timestamp: new Date().toISOString(),
860
+ });
861
+ this.logger.warn(`Tool rejected during reload: ${tool.name}`, {
862
+ reason: policyDecision.reason,
863
+ });
864
+ continue;
865
+ }
866
+ if (policyDecision.allowed === 'pending_approval') {
867
+ // Quarantine: mark as pending in the approval manifest
868
+ if (__classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f")) {
869
+ __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").markPending(tool.name);
870
+ }
871
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
872
+ type: 'tool:quarantined',
873
+ toolName: tool.name,
874
+ riskLevel: policyDecision.riskLevel,
875
+ reason: policyDecision.reason,
876
+ timestamp: new Date().toISOString(),
877
+ });
878
+ this.logger.info(`Tool quarantined during reload: ${tool.name}`, {
879
+ riskLevel: policyDecision.riskLevel,
880
+ reason: policyDecision.reason,
881
+ });
882
+ // Still register the tool so it exists, but it will be blocked
883
+ // at execution time until approved via the approval manifest
884
+ result.revalidated++;
885
+ }
886
+ else {
887
+ result.revalidated++;
888
+ }
889
+ }
890
+ this.registry.register(tool);
891
+ const source = isUntrusted ? 'untrusted' : 'trusted';
892
+ __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").record(tool.name, JSON.stringify(tool), source);
893
+ result.loaded++;
894
+ }
895
+ // Calculate removed tools
896
+ const currentNames = new Set(this.registry.getAll().map((t) => t.name));
897
+ for (const name of previousNames) {
898
+ if (!currentNames.has(name)) {
899
+ result.removed++;
900
+ __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").removeEntry(name);
901
+ }
902
+ }
903
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
904
+ type: 'tools:reloaded',
905
+ loaded: result.loaded,
906
+ removed: result.removed,
907
+ rejected: result.rejected,
908
+ timestamp: new Date().toISOString(),
909
+ });
910
+ this.logger.info('Tools reloaded', {
911
+ loaded: result.loaded,
912
+ removed: result.removed,
913
+ revalidated: result.revalidated,
914
+ rejected: result.rejected.length,
915
+ });
916
+ return result;
917
+ }
918
+ /**
919
+ * Check if a policy engine is active.
920
+ */
921
+ hasPolicy() {
922
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f") !== null;
923
+ }
924
+ /**
925
+ * Get the approval manifest (if policy engine is active).
926
+ */
927
+ getApprovalManifest() {
928
+ return __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f");
929
+ }
930
+ /**
931
+ * Get the integrity tracker.
932
+ */
933
+ getIntegrityTracker() {
934
+ return __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f");
935
+ }
936
+ /**
937
+ * Get the tool registry (for advanced use cases).
938
+ */
939
+ getRegistry() {
940
+ return this.registry;
941
+ }
942
+ /**
943
+ * Set a Human-in-the-Loop callback for quarantined tools.
944
+ * The callback is invoked when a tool with `pending_approval` status is executed.
945
+ * Return `true` to approve, `false` to reject.
946
+ */
947
+ setHITLCallback(callback) {
948
+ __classPrivateFieldSet(this, _MatimoInstance_hitlCallback, callback, "f");
949
+ }
950
+ /**
951
+ * Hot-reload the policy engine at runtime.
952
+ *
953
+ * - If `configOrFile` is a `PolicyConfig` object, creates a new `DefaultPolicyEngine`.
954
+ * - If `configOrFile` is a string, re-reads and parses the YAML file.
955
+ * - If omitted and the instance was initialized with `policyFile`, re-reads that file.
956
+ *
957
+ * The new policy is validated before swap — if validation fails, the old policy remains active.
958
+ * After swap, all tools are re-validated against the new policy via `reloadTools()`.
959
+ *
960
+ * @returns The ReloadResult from the subsequent tool re-validation.
961
+ */
962
+ async reloadPolicy(configOrFile) {
963
+ let newPolicy;
964
+ if (typeof configOrFile === 'string') {
965
+ // Re-read from file path
966
+ newPolicy = loadPolicyFromFile(configOrFile);
967
+ __classPrivateFieldSet(this, _MatimoInstance_policyFile, configOrFile, "f");
968
+ }
969
+ else if (configOrFile) {
970
+ // Build from inline config
971
+ newPolicy = new DefaultPolicyEngine(configOrFile);
972
+ }
973
+ else if (__classPrivateFieldGet(this, _MatimoInstance_policyFile, "f")) {
974
+ // Re-read the original policy file
975
+ newPolicy = loadPolicyFromFile(__classPrivateFieldGet(this, _MatimoInstance_policyFile, "f"));
976
+ }
977
+ else if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && 'updateConfig' in __classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
978
+ // No config provided and no file — nothing to reload
979
+ this.logger.warn('reloadPolicy: no config or file provided, nothing to reload');
980
+ return {
981
+ loaded: 0,
982
+ removed: 0,
983
+ revalidated: 0,
984
+ rejected: [],
985
+ };
986
+ }
987
+ else {
988
+ this.logger.warn('reloadPolicy: no policy engine to reload');
989
+ return {
990
+ loaded: 0,
991
+ removed: 0,
992
+ revalidated: 0,
993
+ rejected: [],
994
+ };
995
+ }
996
+ // Atomic swap: replace the policy engine reference
997
+ __classPrivateFieldSet(this, _MatimoInstance_policy, newPolicy, "f");
998
+ Object.freeze(__classPrivateFieldGet(this, _MatimoInstance_policy, "f"));
999
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
1000
+ type: 'policy:reloaded',
1001
+ timestamp: new Date().toISOString(),
1002
+ });
1003
+ this.logger.info('Policy engine reloaded');
1004
+ // Re-validate all tools against the new policy
1005
+ return this.reloadTools();
1006
+ }
473
1007
  }
1008
+ _MatimoInstance_policy = new WeakMap(), _MatimoInstance_integrityTracker = new WeakMap(), _MatimoInstance_approvalManifest = new WeakMap(), _MatimoInstance_onEvent = new WeakMap(), _MatimoInstance_hitlCallback = new WeakMap(), _MatimoInstance_trustedPaths = new WeakMap(), _MatimoInstance_untrustedPaths = new WeakMap(), _MatimoInstance_policyFile = new WeakMap(), _MatimoInstance_instances = new WeakSet(), _MatimoInstance_emitEvent = function _MatimoInstance_emitEvent(event) {
1009
+ if (__classPrivateFieldGet(this, _MatimoInstance_onEvent, "f")) {
1010
+ try {
1011
+ __classPrivateFieldGet(this, _MatimoInstance_onEvent, "f").call(this, event);
1012
+ }
1013
+ catch {
1014
+ // Never let event handler errors break SDK execution
1015
+ }
1016
+ }
1017
+ }, _MatimoInstance_resolveHITL =
1018
+ /**
1019
+ * Resolve a quarantined tool via HITL callback or approval manifest.
1020
+ *
1021
+ * Resolution order:
1022
+ * 1. Check approval manifest — if already approved, allow.
1023
+ * 2. Invoke HITL callback — if set, ask the human.
1024
+ * 3. If no callback, reject by default (safe fail-closed).
1025
+ */
1026
+ async function _MatimoInstance_resolveHITL(tool, decision, context) {
1027
+ // 1. Check approval manifest — previously approved tools pass through
1028
+ if (__classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f")) {
1029
+ const yamlHash = __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").getHash(tool.name);
1030
+ if (yamlHash && __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").isApproved(tool.name, yamlHash)) {
1031
+ return true;
1032
+ }
1033
+ }
1034
+ // 2. Invoke HITL callback
1035
+ if (__classPrivateFieldGet(this, _MatimoInstance_hitlCallback, "f")) {
1036
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
1037
+ type: 'tool:quarantined',
1038
+ toolName: tool.name,
1039
+ riskLevel: decision.riskLevel,
1040
+ reason: decision.reason,
1041
+ environment: context.environment,
1042
+ timestamp: new Date().toISOString(),
1043
+ });
1044
+ const approved = await __classPrivateFieldGet(this, _MatimoInstance_hitlCallback, "f").call(this, {
1045
+ toolName: tool.name,
1046
+ riskLevel: decision.riskLevel,
1047
+ reason: decision.reason,
1048
+ environment: context.environment,
1049
+ agentId: context.agentId,
1050
+ toolDefinition: tool,
1051
+ });
1052
+ // If approved, record in manifest for future calls
1053
+ if (approved && __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f")) {
1054
+ const yamlHash = __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").getHash(tool.name) ??
1055
+ __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").computeHash(JSON.stringify(tool));
1056
+ __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").approve(tool.name, yamlHash);
1057
+ }
1058
+ return approved;
1059
+ }
1060
+ // 3. No callback — fail closed
1061
+ return false;
1062
+ };
474
1063
  /**
475
1064
  * Matimo namespace - Entry point for the SDK
476
1065
  */