@matimo/core 0.1.0-alpha.8 → 0.1.0

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 (173) hide show
  1. package/README.md +341 -14
  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 +41 -10
  7. package/dist/core/schema.d.ts.map +1 -1
  8. package/dist/core/schema.js +40 -4
  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/tool-loader.d.ts +3 -1
  27. package/dist/core/tool-loader.d.ts.map +1 -1
  28. package/dist/core/tool-loader.js +33 -10
  29. package/dist/core/tool-loader.js.map +1 -1
  30. package/dist/core/types.d.ts +203 -6
  31. package/dist/core/types.d.ts.map +1 -1
  32. package/dist/encodings/parameter-encoding.d.ts +1 -1
  33. package/dist/encodings/parameter-encoding.d.ts.map +1 -1
  34. package/dist/encodings/parameter-encoding.js +9 -4
  35. package/dist/encodings/parameter-encoding.js.map +1 -1
  36. package/dist/errors/matimo-error.d.ts +11 -2
  37. package/dist/errors/matimo-error.d.ts.map +1 -1
  38. package/dist/errors/matimo-error.js +25 -1
  39. package/dist/errors/matimo-error.js.map +1 -1
  40. package/dist/executors/command-executor.d.ts +9 -2
  41. package/dist/executors/command-executor.d.ts.map +1 -1
  42. package/dist/executors/command-executor.js +29 -5
  43. package/dist/executors/command-executor.js.map +1 -1
  44. package/dist/executors/function-executor.d.ts +10 -3
  45. package/dist/executors/function-executor.d.ts.map +1 -1
  46. package/dist/executors/function-executor.js +44 -24
  47. package/dist/executors/function-executor.js.map +1 -1
  48. package/dist/executors/http-executor.d.ts +79 -4
  49. package/dist/executors/http-executor.d.ts.map +1 -1
  50. package/dist/executors/http-executor.js +232 -28
  51. package/dist/executors/http-executor.js.map +1 -1
  52. package/dist/index.d.ts +25 -3
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +19 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/integrations/langchain.d.ts +55 -0
  57. package/dist/integrations/langchain.d.ts.map +1 -1
  58. package/dist/integrations/langchain.js +71 -4
  59. package/dist/integrations/langchain.js.map +1 -1
  60. package/dist/logging/logger.d.ts +8 -2
  61. package/dist/logging/logger.d.ts.map +1 -1
  62. package/dist/logging/logger.js.map +1 -1
  63. package/dist/logging/winston-logger.d.ts.map +1 -1
  64. package/dist/logging/winston-logger.js +9 -1
  65. package/dist/logging/winston-logger.js.map +1 -1
  66. package/dist/matimo-instance.d.ts +230 -18
  67. package/dist/matimo-instance.d.ts.map +1 -1
  68. package/dist/matimo-instance.js +739 -40
  69. package/dist/matimo-instance.js.map +1 -1
  70. package/dist/mcp/index.d.ts +18 -0
  71. package/dist/mcp/index.d.ts.map +1 -0
  72. package/dist/mcp/index.js +24 -0
  73. package/dist/mcp/index.js.map +1 -0
  74. package/dist/mcp/mcp-server.d.ts +141 -0
  75. package/dist/mcp/mcp-server.d.ts.map +1 -0
  76. package/dist/mcp/mcp-server.js +754 -0
  77. package/dist/mcp/mcp-server.js.map +1 -0
  78. package/dist/mcp/secrets/aws-resolver.d.ts +41 -0
  79. package/dist/mcp/secrets/aws-resolver.d.ts.map +1 -0
  80. package/dist/mcp/secrets/aws-resolver.js +141 -0
  81. package/dist/mcp/secrets/aws-resolver.js.map +1 -0
  82. package/dist/mcp/secrets/dotenv-resolver.d.ts +23 -0
  83. package/dist/mcp/secrets/dotenv-resolver.d.ts.map +1 -0
  84. package/dist/mcp/secrets/dotenv-resolver.js +94 -0
  85. package/dist/mcp/secrets/dotenv-resolver.js.map +1 -0
  86. package/dist/mcp/secrets/env-resolver.d.ts +14 -0
  87. package/dist/mcp/secrets/env-resolver.d.ts.map +1 -0
  88. package/dist/mcp/secrets/env-resolver.js +27 -0
  89. package/dist/mcp/secrets/env-resolver.js.map +1 -0
  90. package/dist/mcp/secrets/index.d.ts +14 -0
  91. package/dist/mcp/secrets/index.d.ts.map +1 -0
  92. package/dist/mcp/secrets/index.js +13 -0
  93. package/dist/mcp/secrets/index.js.map +1 -0
  94. package/dist/mcp/secrets/resolver-chain.d.ts +34 -0
  95. package/dist/mcp/secrets/resolver-chain.d.ts.map +1 -0
  96. package/dist/mcp/secrets/resolver-chain.js +141 -0
  97. package/dist/mcp/secrets/resolver-chain.js.map +1 -0
  98. package/dist/mcp/secrets/types.d.ts +73 -0
  99. package/dist/mcp/secrets/types.d.ts.map +1 -0
  100. package/dist/mcp/secrets/types.js +8 -0
  101. package/dist/mcp/secrets/types.js.map +1 -0
  102. package/dist/mcp/secrets/vault-resolver.d.ts +43 -0
  103. package/dist/mcp/secrets/vault-resolver.d.ts.map +1 -0
  104. package/dist/mcp/secrets/vault-resolver.js +127 -0
  105. package/dist/mcp/secrets/vault-resolver.js.map +1 -0
  106. package/dist/mcp/tool-converter.d.ts +40 -0
  107. package/dist/mcp/tool-converter.d.ts.map +1 -0
  108. package/dist/mcp/tool-converter.js +185 -0
  109. package/dist/mcp/tool-converter.js.map +1 -0
  110. package/dist/policy/approval-manifest.d.ts +76 -0
  111. package/dist/policy/approval-manifest.d.ts.map +1 -0
  112. package/dist/policy/approval-manifest.js +197 -0
  113. package/dist/policy/approval-manifest.js.map +1 -0
  114. package/dist/policy/content-validator.d.ts +19 -0
  115. package/dist/policy/content-validator.d.ts.map +1 -0
  116. package/dist/policy/content-validator.js +196 -0
  117. package/dist/policy/content-validator.js.map +1 -0
  118. package/dist/policy/default-policy.d.ts +46 -0
  119. package/dist/policy/default-policy.d.ts.map +1 -0
  120. package/dist/policy/default-policy.js +241 -0
  121. package/dist/policy/default-policy.js.map +1 -0
  122. package/dist/policy/events.d.ts +71 -0
  123. package/dist/policy/events.d.ts.map +1 -0
  124. package/dist/policy/events.js +8 -0
  125. package/dist/policy/events.js.map +1 -0
  126. package/dist/policy/index.d.ts +13 -0
  127. package/dist/policy/index.d.ts.map +1 -0
  128. package/dist/policy/index.js +9 -0
  129. package/dist/policy/index.js.map +1 -0
  130. package/dist/policy/integrity-tracker.d.ts +62 -0
  131. package/dist/policy/integrity-tracker.d.ts.map +1 -0
  132. package/dist/policy/integrity-tracker.js +79 -0
  133. package/dist/policy/integrity-tracker.js.map +1 -0
  134. package/dist/policy/policy-loader.d.ts +58 -0
  135. package/dist/policy/policy-loader.d.ts.map +1 -0
  136. package/dist/policy/policy-loader.js +156 -0
  137. package/dist/policy/policy-loader.js.map +1 -0
  138. package/dist/policy/risk-classifier.d.ts +18 -0
  139. package/dist/policy/risk-classifier.d.ts.map +1 -0
  140. package/dist/policy/risk-classifier.js +47 -0
  141. package/dist/policy/risk-classifier.js.map +1 -0
  142. package/dist/policy/types.d.ts +131 -0
  143. package/dist/policy/types.d.ts.map +1 -0
  144. package/dist/policy/types.js +8 -0
  145. package/dist/policy/types.js.map +1 -0
  146. package/package.json +22 -6
  147. package/tools/matimo_approve_tool/definition.yaml +36 -0
  148. package/tools/matimo_approve_tool/matimo_approve_tool.ts +90 -0
  149. package/tools/matimo_create_skill/definition.yaml +46 -0
  150. package/tools/matimo_create_skill/matimo_create_skill.ts +75 -0
  151. package/tools/matimo_create_tool/definition.yaml +48 -0
  152. package/tools/matimo_create_tool/matimo_create_tool.ts +137 -0
  153. package/tools/matimo_get_skill/definition.yaml +60 -0
  154. package/tools/matimo_get_skill/matimo_get_skill.ts +182 -0
  155. package/tools/matimo_get_tool/definition.yaml +36 -0
  156. package/tools/matimo_get_tool/matimo_get_tool.ts +56 -0
  157. package/tools/matimo_get_tool_status/definition.yaml +42 -0
  158. package/tools/matimo_get_tool_status/matimo_get_tool_status.ts +101 -0
  159. package/tools/matimo_list_skills/definition.yaml +52 -0
  160. package/tools/matimo_list_skills/matimo_list_skills.ts +138 -0
  161. package/tools/matimo_list_user_tools/definition.yaml +32 -0
  162. package/tools/matimo_list_user_tools/matimo_list_user_tools.ts +74 -0
  163. package/tools/matimo_reload_tools/definition.yaml +35 -0
  164. package/tools/matimo_reload_tools/matimo_reload_tools.ts +29 -0
  165. package/tools/matimo_search_tools/definition.yaml +32 -0
  166. package/tools/matimo_search_tools/matimo_search_tools.ts +82 -0
  167. package/tools/matimo_validate_skill/definition.yaml +43 -0
  168. package/tools/matimo_validate_skill/matimo_validate_skill.ts +137 -0
  169. package/tools/matimo_validate_tool/definition.yaml +34 -0
  170. package/tools/matimo_validate_tool/matimo_validate_tool.ts +168 -0
  171. package/tools/read/read.ts +0 -2
  172. package/tools/shared/skill-validation.ts +335 -0
  173. package/LICENSE +0 -21
@@ -1,28 +1,104 @@
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, _a, _MatimoInstance_policy, _MatimoInstance_integrityTracker, _MatimoInstance_approvalManifest, _MatimoInstance_onEvent, _MatimoInstance_hitlCallback, _MatimoInstance_hitlTimeoutMs, _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_hitlTimeoutMs.set(this, void 0);
64
+ _MatimoInstance_trustedPaths.set(this, void 0);
65
+ _MatimoInstance_untrustedPaths.set(this, void 0);
66
+ _MatimoInstance_policyFile.set(this, void 0);
16
67
  this.toolPaths = toolPaths;
68
+ this.skillPaths = skillPaths;
17
69
  this.logger = logger;
18
70
  this.loader = new ToolLoader();
19
71
  this.registry = new ToolRegistry();
72
+ this.skillLoader = new SkillLoader();
73
+ this.skillRegistry = new SkillRegistry();
20
74
  // Use the first path (primary) as working directory for command executor
21
75
  const workingDir = toolPaths.length > 0 ? path.dirname(toolPaths[0]) : process.cwd();
22
76
  this.commandExecutor = new CommandExecutor(workingDir);
23
77
  this.httpExecutor = new HttpExecutor();
24
78
  this.functionExecutor = new FunctionExecutor(toolPaths[0] || '');
25
79
  this.approvalHandler = getGlobalApprovalHandler();
80
+ // Policy engine setup
81
+ __classPrivateFieldSet(this, _MatimoInstance_policy, policyOptions?.policy ?? null, "f");
82
+ __classPrivateFieldSet(this, _MatimoInstance_trustedPaths, policyOptions?.trustedPaths ?? [], "f");
83
+ __classPrivateFieldSet(this, _MatimoInstance_untrustedPaths, policyOptions?.untrustedPaths ?? [], "f");
84
+ __classPrivateFieldSet(this, _MatimoInstance_integrityTracker, new ToolIntegrityTracker(), "f");
85
+ __classPrivateFieldSet(this, _MatimoInstance_onEvent, policyOptions?.onEvent ?? null, "f");
86
+ __classPrivateFieldSet(this, _MatimoInstance_hitlCallback, policyOptions?.onHITL ?? null, "f");
87
+ __classPrivateFieldSet(this, _MatimoInstance_hitlTimeoutMs, policyOptions?.hitlTimeoutMs ?? null, "f");
88
+ __classPrivateFieldSet(this, _MatimoInstance_policyFile, policyOptions?.policyFile ?? null, "f");
89
+ // Approval manifest
90
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
91
+ const approvalDir = policyOptions?.approvalDir ?? process.cwd();
92
+ __classPrivateFieldSet(this, _MatimoInstance_approvalManifest, new ApprovalManifest(approvalDir, policyOptions?.approvalSecret, policyOptions?.approvalTtlSeconds), "f");
93
+ }
94
+ else {
95
+ __classPrivateFieldSet(this, _MatimoInstance_approvalManifest, null, "f");
96
+ }
97
+ // Freeze policy to prevent runtime mutation by agents.
98
+ // (The SDK's own reloadPolicy() bypasses the frozen reference by replacing #policy.)
99
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
100
+ Object.freeze(__classPrivateFieldGet(this, _MatimoInstance_policy, "f"));
101
+ }
26
102
  }
27
103
  /**
28
104
  * Initialize Matimo with tools from directory or auto-discovery
@@ -82,27 +158,85 @@ export class MatimoInstance {
82
158
  autoDiscover: finalOptions.autoDiscover,
83
159
  });
84
160
  const toolPaths = [];
161
+ const skillPaths = [];
85
162
  // Include core tools (calculator, etc.) - currently not used in monorepo
86
163
  // Use explicit toolPaths or autoDiscover instead
87
164
  // if (finalOptions.includeCore) { ... }
88
- // Add explicit paths
165
+ // Add explicit tool paths
89
166
  if (finalOptions.toolPaths) {
90
167
  toolPaths.push(...finalOptions.toolPaths);
91
168
  logger.debug(`Adding explicit tool paths`, { count: finalOptions.toolPaths.length });
92
169
  }
170
+ // Add explicit skill paths (include core skills by default)
171
+ if (finalOptions.skillPaths) {
172
+ skillPaths.push(...finalOptions.skillPaths);
173
+ logger.debug(`Adding explicit skill paths`, { count: finalOptions.skillPaths.length });
174
+ }
175
+ // Always include core skills bundled with Matimo
176
+ const coreSkillsPath = findCoreSkillsPath();
177
+ if (coreSkillsPath && !skillPaths.includes(coreSkillsPath)) {
178
+ skillPaths.push(coreSkillsPath);
179
+ logger.debug(`Including core skills`, { path: coreSkillsPath });
180
+ }
93
181
  // Auto-discover @matimo/* packages
94
182
  if (finalOptions.autoDiscover) {
95
183
  const discoveredPaths = new ToolLoader().autoDiscoverPackages();
96
184
  toolPaths.push(...discoveredPaths);
97
185
  logger.debug(`Auto-discovered tool paths`, { count: discoveredPaths.length });
186
+ // Also discover skill paths from the same @matimo/* packages
187
+ // Each discovered tool path is like @matimo/slack/tools/ — sibling skills/ may exist
188
+ for (const toolPath of discoveredPaths) {
189
+ const pkgDir = path.dirname(toolPath); // e.g. @matimo/slack/
190
+ const pkgSkillsPath = path.join(pkgDir, 'skills');
191
+ if (fs.existsSync(pkgSkillsPath) && !skillPaths.includes(pkgSkillsPath)) {
192
+ skillPaths.push(pkgSkillsPath);
193
+ }
194
+ }
195
+ logger.debug(`Auto-discovered skill paths`, { count: skillPaths.length });
196
+ }
197
+ // Build policy engine
198
+ let policy = null;
199
+ if (finalOptions.policy) {
200
+ policy = finalOptions.policy;
201
+ }
202
+ else if (finalOptions.policyFile) {
203
+ policy = loadPolicyFromFile(finalOptions.policyFile);
98
204
  }
99
- const instance = new MatimoInstance(toolPaths, logger);
205
+ else if (finalOptions.policyConfig) {
206
+ policy = new DefaultPolicyEngine(finalOptions.policyConfig);
207
+ }
208
+ const instance = new _a(toolPaths, skillPaths, logger, {
209
+ policy,
210
+ trustedPaths: finalOptions.trustedPaths,
211
+ untrustedPaths: finalOptions.untrustedPaths,
212
+ approvalSecret: finalOptions.approvalSecret,
213
+ approvalDir: finalOptions.approvalDir,
214
+ onEvent: finalOptions.onEvent,
215
+ onHITL: finalOptions.onHITL,
216
+ hitlTimeoutMs: finalOptions.hitlTimeoutMs,
217
+ approvalTtlSeconds: finalOptions.approvalTtlSeconds,
218
+ policyFile: finalOptions.policyFile,
219
+ });
100
220
  // Load tools from all paths
101
221
  const allTools = instance.loader.loadToolsFromMultiplePaths(toolPaths);
102
222
  instance.registry.registerAll(Array.from(allTools.values()));
223
+ // Load skills from all paths
224
+ for (const skillPath of skillPaths) {
225
+ try {
226
+ const skillsFromPath = instance.skillLoader.loadSkillsFromDirectory(skillPath, skillPath.includes('core') ? 'builtin' : 'user');
227
+ instance.skillRegistry.registerAll(skillsFromPath);
228
+ }
229
+ catch (err) {
230
+ logger.warn(`Failed to load skills from path: ${skillPath}`, {
231
+ error: err.message,
232
+ });
233
+ }
234
+ }
103
235
  logger.info(`Matimo SDK initialized successfully`, {
104
236
  toolCount: allTools.size,
237
+ skillCount: instance.skillRegistry.count(),
105
238
  paths: toolPaths.length,
239
+ skillPaths: skillPaths.length,
106
240
  });
107
241
  return instance;
108
242
  }
@@ -121,12 +255,19 @@ export class MatimoInstance {
121
255
  return this.logger;
122
256
  }
123
257
  /**
124
- * Execute a tool by name with parameters
258
+ * Execute a tool by name with parameters.
259
+ *
125
260
  * @param toolName - Name of the tool to execute
126
261
  * @param params - Tool parameters
262
+ * @param options - Optional execution options
263
+ * @param options.timeout - Execution timeout in milliseconds
264
+ * @param options.credentials - Per-call credential overrides (multi-tenant support).
265
+ * Keys must match the env-var names the tool references (e.g. `SLACK_BOT_TOKEN`).
266
+ * When provided, they take precedence over `process.env` for that single call.
267
+ * Values are never logged and held in memory only for the duration of the call.
127
268
  * @returns Tool execution result
128
269
  */
129
- async execute(toolName, params) {
270
+ async execute(toolName, params, options) {
130
271
  const tool = this.registry.get(toolName);
131
272
  if (!tool) {
132
273
  const availableTools = this.registry.getAll().map((t) => t.name);
@@ -144,6 +285,38 @@ export class MatimoInstance {
144
285
  paramCount: Object.keys(params).length,
145
286
  });
146
287
  try {
288
+ // Policy check: enforce RBAC and tool status before any execution
289
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
290
+ const policyContext = options?.context ?? {};
291
+ const decision = __classPrivateFieldGet(this, _MatimoInstance_policy, "f").canExecute(policyContext, tool);
292
+ if (decision.allowed === false) {
293
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
294
+ type: 'tool:execution_denied',
295
+ toolName,
296
+ reason: decision.reason,
297
+ agentId: policyContext.agentId,
298
+ timestamp: new Date().toISOString(),
299
+ });
300
+ throw new MatimoError(`Policy denied execution of '${toolName}': ${decision.reason}`, ErrorCode.POLICY_DENIED, { toolName, reason: decision.reason, riskLevel: decision.riskLevel });
301
+ }
302
+ // Handle quarantined tools — check approval manifest or invoke HITL callback
303
+ if (decision.allowed === 'pending_approval') {
304
+ const approved = await __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_resolveHITL).call(this, tool, decision, policyContext);
305
+ if (!approved) {
306
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
307
+ type: 'tool:quarantine_rejected',
308
+ toolName,
309
+ timestamp: new Date().toISOString(),
310
+ });
311
+ throw new MatimoError(`Tool '${toolName}' is quarantined and was not approved: ${decision.reason}`, ErrorCode.POLICY_DENIED, { toolName, reason: decision.reason, riskLevel: decision.riskLevel });
312
+ }
313
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
314
+ type: 'tool:quarantine_approved',
315
+ toolName,
316
+ timestamp: new Date().toISOString(),
317
+ });
318
+ }
319
+ }
147
320
  // Simple approval flow:
148
321
  // 1. Check if tool requires approval (from YAML or keyword detection)
149
322
  // 2. Check if pre-approved via env vars
@@ -171,7 +344,12 @@ export class MatimoInstance {
171
344
  scanContent = parts.join(' ');
172
345
  }
173
346
  const requiresApproval = this.approvalHandler.requiresApproval(tool.requires_approval, scanContent);
174
- if (requiresApproval && !this.approvalHandler.isPreApproved(toolName)) {
347
+ // Skip approval prompt if options.approved=true (e.g., MCP pre-confirmed approval).
348
+ // This avoids mutating global state in concurrent scenarios.
349
+ const skipApprovalPrompt = options?.approved === true;
350
+ if (requiresApproval &&
351
+ !this.approvalHandler.isPreApproved(toolName) &&
352
+ !skipApprovalPrompt) {
175
353
  this.logger.debug(`Approval required for: ${toolName}`, { toolName });
176
354
  await this.approvalHandler.requestApproval({
177
355
  toolName,
@@ -180,10 +358,42 @@ export class MatimoInstance {
180
358
  });
181
359
  this.logger.info(`Destructive operation approved: ${toolName}`, { toolName });
182
360
  }
183
- // Auto-inject authentication parameters from environment variables
184
- const finalParams = this.injectAuthParameters(tool, params);
185
- const executor = this.getExecutor(tool);
186
- const result = await executor.execute(tool, finalParams);
361
+ const credentials = options?.credentials;
362
+ const timeoutOverride = options?.timeout;
363
+ // Auto-inject authentication parameters. When per-call credentials are
364
+ // supplied they take precedence over process.env (multi-tenant support).
365
+ const finalParams = this.injectAuthParameters(tool, params, credentials);
366
+ // After injection, detect any auth-looking placeholders that are still unfilled.
367
+ // This gives a clear actionable error instead of silently sending a bad header to the API.
368
+ this.assertAuthParamsFilled(tool, finalParams, credentials);
369
+ // Apply per-call timeout override if provided. Create a shallow copy so the
370
+ // registered tool definition is never mutated between calls.
371
+ // Built-in interception: matimo_reload_tools must run on the instance
372
+ // itself because reloadTools() clears/rebuilds the in-memory registry.
373
+ // The function executor has no reference to the MatimoInstance, so we
374
+ // handle it directly here. This works identically for SDK, LangChain,
375
+ // and MCP callers.
376
+ if (toolName === 'matimo_reload_tools') {
377
+ const reloadResult = await this.reloadTools();
378
+ this.logger.info('matimo_reload_tools: reload completed', {
379
+ loaded: reloadResult.loaded,
380
+ removed: reloadResult.removed,
381
+ rejected: reloadResult.rejected.length,
382
+ });
383
+ return {
384
+ success: true,
385
+ loaded: reloadResult.loaded,
386
+ removed: reloadResult.removed,
387
+ revalidated: reloadResult.revalidated,
388
+ rejected: reloadResult.rejected,
389
+ message: `Reload complete. ${reloadResult.loaded} tools loaded, ${reloadResult.removed} removed, ${reloadResult.rejected.length} rejected.`,
390
+ };
391
+ }
392
+ const effectiveTool = timeoutOverride !== undefined
393
+ ? { ...tool, execution: { ...tool.execution, timeout: timeoutOverride } }
394
+ : tool;
395
+ const executor = this.getExecutor(effectiveTool);
396
+ const result = await executor.execute(effectiveTool, finalParams, credentials);
187
397
  this.logger.debug(`Tool executed successfully: ${toolName}`, {
188
398
  toolName,
189
399
  hasResult: !!result,
@@ -207,34 +417,201 @@ export class MatimoInstance {
207
417
  return this.registry.get(toolName);
208
418
  }
209
419
  /**
210
- * List all available tools
420
+ * List all available tools, optionally filtered by policy.
421
+ * @param context - PolicyContext for filtering. If omitted and policy is active, returns all tools (backward compatible).
211
422
  * @returns Array of tool definitions
212
423
  */
213
- listTools() {
214
- return this.registry.getAll();
424
+ listTools(context) {
425
+ const tools = this.registry.getAll();
426
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && context) {
427
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f").filterForAgent(context, tools);
428
+ }
429
+ return tools;
215
430
  }
216
431
  /**
217
432
  * Get all available tools (alias for listTools)
218
433
  * @returns Array of tool definitions
219
434
  */
220
- getAllTools() {
221
- return this.registry.getAll();
435
+ getAllTools(context) {
436
+ return this.listTools(context);
437
+ }
438
+ /**
439
+ * Return only the tools this agent context is permitted to use.
440
+ * Mirrors: Matimo.get_tools_for_agent() in Python SDK.
441
+ *
442
+ * @param context - PolicyContext (agentId, roles, environment)
443
+ * @returns Tools permitted for this context, filtered through the policy engine
444
+ */
445
+ getToolsForAgent(context) {
446
+ const tools = this.registry.getAll();
447
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
448
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f").filterForAgent(context, tools);
449
+ }
450
+ return tools;
222
451
  }
223
452
  /**
224
453
  * Search tools by name or description
225
454
  * @param query - Search query
226
455
  * @returns Matching tools
227
456
  */
228
- searchTools(query) {
229
- return this.registry.search(query);
457
+ searchTools(query, context) {
458
+ const results = this.registry.search(query);
459
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && context) {
460
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f").filterForAgent(context, results);
461
+ }
462
+ return results;
230
463
  }
231
464
  /**
232
465
  * Get tools by tag
233
466
  * @param tag - Tag to search for
234
467
  * @returns Tools with the given tag
235
468
  */
236
- getToolsByTag(tag) {
237
- return this.registry.getByTag(tag);
469
+ getToolsByTag(tag, context) {
470
+ const results = this.registry.getByTag(tag);
471
+ if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && context) {
472
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f").filterForAgent(context, results);
473
+ }
474
+ return results;
475
+ }
476
+ /**
477
+ * Return the credential key names that a tool expects.
478
+ *
479
+ * This lets multi-tenant callers know exactly what to put in `options.credentials`
480
+ * without having to read the tool's YAML definition.
481
+ *
482
+ * The returned strings are the keys you pass to `execute()`:
483
+ * ```typescript
484
+ * const keys = matimo.getRequiredCredentials('slack-send-message');
485
+ * // → ['SLACK_BOT_TOKEN']
486
+ *
487
+ * // Then collect from your secrets store:
488
+ * const credentials = Object.fromEntries(
489
+ * keys.map(k => [k, tenant.secrets[k]])
490
+ * );
491
+ * await matimo.execute('slack-send-message', params, { credentials });
492
+ * ```
493
+ *
494
+ * @param toolName - Exact tool name
495
+ * @returns Array of credential key names (may be empty if the tool needs no auth)
496
+ * @throws `MatimoError(TOOL_NOT_FOUND)` if the tool doesn't exist
497
+ */
498
+ getRequiredCredentials(toolName) {
499
+ const tool = this.registry.get(toolName);
500
+ if (!tool) {
501
+ throw new MatimoError(`Tool '${toolName}' not found in registry`, ErrorCode.TOOL_NOT_FOUND, {
502
+ toolName,
503
+ availableTools: this.registry.getAll().map((t) => t.name),
504
+ });
505
+ }
506
+ const authPatterns = [
507
+ 'token',
508
+ 'key',
509
+ 'secret',
510
+ 'password',
511
+ 'credential',
512
+ 'auth',
513
+ 'bearer',
514
+ 'api_key',
515
+ ];
516
+ const referencedParams = this.extractParameterPlaceholders(tool);
517
+ const credentialKeys = [];
518
+ for (const paramName of referencedParams) {
519
+ const lowerName = paramName.toLowerCase();
520
+ if (authPatterns.some((pattern) => lowerName.includes(pattern))) {
521
+ credentialKeys.push(paramName);
522
+ }
523
+ }
524
+ // Also include basic-auth env var names declared in authentication config
525
+ const auth = tool.authentication;
526
+ if (auth?.type === 'basic') {
527
+ if (auth.username_env && !credentialKeys.includes(auth.username_env)) {
528
+ credentialKeys.push(auth.username_env);
529
+ }
530
+ if (auth.password_env && !credentialKeys.includes(auth.password_env)) {
531
+ credentialKeys.push(auth.password_env);
532
+ }
533
+ }
534
+ return credentialKeys;
535
+ }
536
+ // ─── Skills API ──────────────────────────────────────────────────────────
537
+ /**
538
+ * List all available skills (Level 1 discovery - minimal context)
539
+ * @returns Array of skill summaries
540
+ */
541
+ listSkills() {
542
+ return this.skillRegistry.list();
543
+ }
544
+ /**
545
+ * Get a single skill by name (Level 2 activation - full content)
546
+ * @param name - Skill name
547
+ * @returns Skill definition or null
548
+ */
549
+ getSkill(name) {
550
+ return this.skillRegistry.get(name) || null;
551
+ }
552
+ /**
553
+ * Get selective skill content — only the sections an agent needs.
554
+ * Prevents dumping entire SKILL.md files into the LLM context window.
555
+ *
556
+ * @example
557
+ * // Get only error handling, max 500 tokens
558
+ * matimo.getSkillContent('postgres-query-operations', {
559
+ * sections: ['Error Handling'],
560
+ * maxTokens: 500,
561
+ * })
562
+ */
563
+ getSkillContent(name, options) {
564
+ return this.skillRegistry.getSkillContent(name, options);
565
+ }
566
+ /**
567
+ * List all sections of a skill with their token costs.
568
+ * Agents use this to decide which sections to load before activating.
569
+ */
570
+ getSkillSections(name) {
571
+ return this.skillRegistry.getSkillSections(name);
572
+ }
573
+ /**
574
+ * Search skills by keyword, category, difficulty, etc.
575
+ * Set `options.semantic = true` for embedding-based similarity ranking.
576
+ * @param options - Search options
577
+ * @returns Matching skills
578
+ */
579
+ searchSkills(options = {}) {
580
+ return this.skillRegistry.search(options);
581
+ }
582
+ /**
583
+ * Semantic search with relevance scores.
584
+ * Uses embeddings to find skills by meaning, not just keywords.
585
+ *
586
+ * @example
587
+ * const results = await matimo.semanticSearchSkills('How do I handle Postgres locking?');
588
+ * // → [{ skill: { name: 'postgres-query-operations' }, score: 0.82 }]
589
+ */
590
+ async semanticSearchSkills(query, options) {
591
+ return this.skillRegistry.semanticSearch(query, options);
592
+ }
593
+ /**
594
+ * Set a custom embedding provider for semantic skill search.
595
+ * If not set, a built-in TF-IDF provider is used.
596
+ */
597
+ setSkillEmbeddingProvider(provider) {
598
+ this.skillRegistry.setEmbeddingProvider(provider);
599
+ }
600
+ /**
601
+ * Get a bundled resource from a skill (Level 3 resources)
602
+ * @param skillName - Skill name
603
+ * @param resourcePath - Relative path to resource (e.g., "scripts/extract.py")
604
+ * @returns Resource content
605
+ */
606
+ getSkillResource(skillName, resourcePath) {
607
+ return this.skillLoader.loadSkillResource(skillName, this.skillPaths[0], resourcePath);
608
+ }
609
+ /**
610
+ * Get all skill paths
611
+ * @returns Array of skill paths
612
+ */
613
+ getSkillPaths() {
614
+ return [...this.skillPaths];
238
615
  }
239
616
  /**
240
617
  * Automatically inject parameters from environment variables
@@ -242,19 +619,15 @@ export class MatimoInstance {
242
619
  *
243
620
  * 1. Scans the execution config for all parameter placeholders
244
621
  * 2. For each parameter not provided by user, checks if it looks like auth (TOKEN, KEY, SECRET, etc.)
245
- * 3. If yes, attempts to load from environment: MATIMO_<PARAM_NAME> or <PARAM_NAME>
246
- *
247
- * This works for ANY tool with ANY auth parameter name - no hardcoding needed.
248
- * Scales to unlimited tools - contributors just submit YAML.
622
+ * 3. If yes, attempts to load from (in order of priority):
623
+ * a. `credentials[paramName]` — per-call override (multi-tenant)
624
+ * b. `credentials[MATIMO_${paramName}]` prefixed per-call override
625
+ * c. `process.env[MATIMO_${paramName}]` prefixed env var
626
+ * d. `process.env[paramName]` — direct env var
249
627
  *
250
- * Examples:
251
- * - GMAIL_ACCESS_TOKEN → looks in env vars
252
- * - GITHUB_TOKEN → looks in env vars
253
- * - SLACK_BOT_TOKEN → looks in env vars
254
- * - MY_CUSTOM_API_KEY → looks in env vars
255
- * - ANY_SECRET → looks in env vars
256
- */
257
- injectAuthParameters(tool, params) {
628
+ * Credential values are never logged.
629
+ */
630
+ injectAuthParameters(tool, params, credentials) {
258
631
  const result = { ...params };
259
632
  // Collect all parameter names referenced in the execution config
260
633
  const referencedParams = this.extractParameterPlaceholders(tool);
@@ -279,21 +652,79 @@ export class MatimoInstance {
279
652
  const lowerName = paramName.toLowerCase();
280
653
  const isAuthParam = authPatterns.some((pattern) => lowerName.includes(pattern));
281
654
  if (isAuthParam) {
282
- // Try to load from environment
283
- // First try MATIMO_ prefixed version for organization
284
- let envValue = process.env[`MATIMO_${paramName}`];
285
- // If not found, try the parameter name directly
286
- if (!envValue) {
287
- envValue = process.env[paramName];
655
+ // Lookup order:
656
+ // 1. Per-call credentials (multi-tenant override never touches process.env)
657
+ // 2. Per-call credentials with MATIMO_ prefix
658
+ // 3. Environment variable with MATIMO_ prefix
659
+ // 4. Environment variable with the exact param name
660
+ let resolvedValue;
661
+ if (credentials) {
662
+ resolvedValue = credentials[paramName] ?? credentials[`MATIMO_${paramName}`];
663
+ }
664
+ if (!resolvedValue) {
665
+ resolvedValue = process.env[`MATIMO_${paramName}`] ?? process.env[paramName];
288
666
  }
289
- // If found, inject it
290
- if (envValue) {
291
- result[paramName] = envValue;
667
+ if (resolvedValue) {
668
+ result[paramName] = resolvedValue;
292
669
  }
293
670
  }
294
671
  }
295
672
  return result;
296
673
  }
674
+ /**
675
+ * After injectAuthParameters(), verify no auth-looking placeholders remain unfilled.
676
+ * Only checks HTTP headers (where auth credentials are injected) — not query params or body.
677
+ * Throws AUTH_FAILED with actionable guidance naming the missing env var(s).
678
+ */
679
+ assertAuthParamsFilled(tool, finalParams, credentials) {
680
+ const execution = tool.execution;
681
+ if (!('headers' in execution) || !execution.headers)
682
+ return;
683
+ const authPatterns = [
684
+ 'token',
685
+ 'key',
686
+ 'secret',
687
+ 'password',
688
+ 'credential',
689
+ 'auth',
690
+ 'bearer',
691
+ 'api_key',
692
+ ];
693
+ const placeholderRegex = /\{([^}]+)\}/g;
694
+ const missing = [];
695
+ // Only inspect headers — auth credentials belong there, not in query_params or body
696
+ for (const [headerName, headerValue] of Object.entries(execution.headers)) {
697
+ if (typeof headerValue !== 'string')
698
+ continue;
699
+ // Only check headers that look auth-related (Authorization, X-API-Key, etc.)
700
+ const lowerHeader = headerName.toLowerCase();
701
+ const isAuthHeader = lowerHeader === 'authorization' ||
702
+ lowerHeader.includes('auth') ||
703
+ lowerHeader.includes('token') ||
704
+ lowerHeader.includes('key') ||
705
+ lowerHeader.includes('secret');
706
+ if (!isAuthHeader)
707
+ continue;
708
+ let match;
709
+ placeholderRegex.lastIndex = 0;
710
+ while ((match = placeholderRegex.exec(headerValue)) !== null) {
711
+ const paramName = match[1];
712
+ if (paramName in finalParams)
713
+ continue;
714
+ const lowerName = paramName.toLowerCase();
715
+ if (authPatterns.some((p) => lowerName.includes(p))) {
716
+ missing.push(paramName);
717
+ }
718
+ }
719
+ }
720
+ if (missing.length === 0)
721
+ return;
722
+ const hints = missing
723
+ .map((n) => ` • ${n} → MATIMO_${n} (or pass via credentials option)`)
724
+ .join('\n');
725
+ const credentialsHint = credentials ? '' : ' (No per-call credentials were supplied.)';
726
+ throw new MatimoError(`Authentication credentials are missing for tool "${tool.name}".\n${hints}\n${credentialsHint}`.trim(), ErrorCode.AUTH_FAILED, { toolName: tool.name, missingCredentials: missing });
727
+ }
297
728
  /**
298
729
  * Extract all parameter placeholders from execution config
299
730
  * Scans headers, body, URL, and query_params for {paramName} patterns
@@ -395,7 +826,275 @@ export class MatimoInstance {
395
826
  throw new MatimoError(`Unsupported execution type: ${executionType}`, ErrorCode.EXECUTION_FAILED, { executionType });
396
827
  }
397
828
  }
829
+ /**
830
+ * Hot-reload tools from all configured paths.
831
+ * Re-validates untrusted tools via content validator and integrity tracker.
832
+ * Tools that fail validation are rejected and not loaded.
833
+ *
834
+ * Atomic: if loading fails mid-way (e.g. I/O error), the registry is restored
835
+ * to its previous state and `rolledBack: true` is included in the result.
836
+ */
837
+ async reloadTools() {
838
+ const previousNames = new Set(this.registry.getAll().map((t) => t.name));
839
+ // Snapshot the previous registry state for rollback on partial failure
840
+ const snapshot = this.registry.getAll();
841
+ this.registry.clear();
842
+ const result = {
843
+ loaded: 0,
844
+ removed: 0,
845
+ revalidated: 0,
846
+ rejected: [],
847
+ rolledBack: false,
848
+ };
849
+ let allTools;
850
+ try {
851
+ allTools = this.loader.loadToolsFromMultiplePaths(this.toolPaths);
852
+ }
853
+ catch (err) {
854
+ // I/O failure during load — restore snapshot and signal rollback
855
+ this.registry.registerAll(snapshot);
856
+ result.rolledBack = true;
857
+ this.logger.error('reloadTools: failed to load tools, rolled back to previous state', {
858
+ error: err.message,
859
+ });
860
+ return result;
861
+ }
862
+ const untrustedSet = new Set(__classPrivateFieldGet(this, _MatimoInstance_untrustedPaths, "f"));
863
+ for (const [, tool] of allTools) {
864
+ const defPath = tool._definitionPath ?? '';
865
+ const isUntrusted = untrustedSet.size > 0 && __classPrivateFieldGet(this, _MatimoInstance_untrustedPaths, "f").some((up) => defPath.startsWith(up));
866
+ if (isUntrusted && __classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
867
+ // Run policy validation on untrusted tools
868
+ const policyDecision = __classPrivateFieldGet(this, _MatimoInstance_policy, "f").canCreate({}, tool);
869
+ if (policyDecision.allowed === false) {
870
+ result.rejected.push(tool.name);
871
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
872
+ type: 'tool:rejected',
873
+ toolName: tool.name,
874
+ violations: [
875
+ { rule: 'policy-denied', severity: 'high', message: policyDecision.reason },
876
+ ],
877
+ timestamp: new Date().toISOString(),
878
+ });
879
+ this.logger.warn(`Tool rejected during reload: ${tool.name}`, {
880
+ reason: policyDecision.reason,
881
+ });
882
+ continue;
883
+ }
884
+ if (policyDecision.allowed === 'pending_approval') {
885
+ // Quarantine: mark as pending in the approval manifest
886
+ if (__classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f")) {
887
+ __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").markPending(tool.name);
888
+ }
889
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
890
+ type: 'tool:quarantined',
891
+ toolName: tool.name,
892
+ riskLevel: policyDecision.riskLevel,
893
+ reason: policyDecision.reason,
894
+ timestamp: new Date().toISOString(),
895
+ });
896
+ this.logger.info(`Tool quarantined during reload: ${tool.name}`, {
897
+ riskLevel: policyDecision.riskLevel,
898
+ reason: policyDecision.reason,
899
+ });
900
+ // Still register the tool so it exists, but it will be blocked
901
+ // at execution time until approved via the approval manifest
902
+ result.revalidated++;
903
+ }
904
+ else {
905
+ result.revalidated++;
906
+ }
907
+ }
908
+ this.registry.register(tool);
909
+ const source = isUntrusted ? 'untrusted' : 'trusted';
910
+ __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").record(tool.name, JSON.stringify(tool), source);
911
+ result.loaded++;
912
+ }
913
+ // Calculate removed tools
914
+ const currentNames = new Set(this.registry.getAll().map((t) => t.name));
915
+ for (const name of previousNames) {
916
+ if (!currentNames.has(name)) {
917
+ result.removed++;
918
+ __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").removeEntry(name);
919
+ }
920
+ }
921
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
922
+ type: 'tools:reloaded',
923
+ loaded: result.loaded,
924
+ removed: result.removed,
925
+ rejected: result.rejected,
926
+ timestamp: new Date().toISOString(),
927
+ });
928
+ this.logger.info('Tools reloaded', {
929
+ loaded: result.loaded,
930
+ removed: result.removed,
931
+ revalidated: result.revalidated,
932
+ rejected: result.rejected.length,
933
+ });
934
+ return result;
935
+ }
936
+ /**
937
+ * Check if a policy engine is active.
938
+ */
939
+ hasPolicy() {
940
+ return __classPrivateFieldGet(this, _MatimoInstance_policy, "f") !== null;
941
+ }
942
+ /**
943
+ * Get the approval manifest (if policy engine is active).
944
+ */
945
+ getApprovalManifest() {
946
+ return __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f");
947
+ }
948
+ /**
949
+ * Get the integrity tracker.
950
+ */
951
+ getIntegrityTracker() {
952
+ return __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f");
953
+ }
954
+ /**
955
+ * Get the tool registry (for advanced use cases).
956
+ */
957
+ getRegistry() {
958
+ return this.registry;
959
+ }
960
+ /**
961
+ * Set a Human-in-the-Loop callback for quarantined tools.
962
+ * The callback is invoked when a tool with `pending_approval` status is executed.
963
+ * Return `true` to approve, `false` to reject.
964
+ */
965
+ setHITLCallback(callback) {
966
+ __classPrivateFieldSet(this, _MatimoInstance_hitlCallback, callback, "f");
967
+ }
968
+ /**
969
+ * Hot-reload the policy engine at runtime.
970
+ *
971
+ * - If `configOrFile` is a `PolicyConfig` object, creates a new `DefaultPolicyEngine`.
972
+ * - If `configOrFile` is a string, re-reads and parses the YAML file.
973
+ * - If omitted and the instance was initialized with `policyFile`, re-reads that file.
974
+ *
975
+ * The new policy is validated before swap — if validation fails, the old policy remains active.
976
+ * After swap, all tools are re-validated against the new policy via `reloadTools()`.
977
+ *
978
+ * @returns The ReloadResult from the subsequent tool re-validation.
979
+ */
980
+ async reloadPolicy(configOrFile) {
981
+ let newPolicy;
982
+ if (typeof configOrFile === 'string') {
983
+ // Re-read from file path
984
+ newPolicy = loadPolicyFromFile(configOrFile);
985
+ __classPrivateFieldSet(this, _MatimoInstance_policyFile, configOrFile, "f");
986
+ }
987
+ else if (configOrFile) {
988
+ // Build from inline config
989
+ newPolicy = new DefaultPolicyEngine(configOrFile);
990
+ }
991
+ else if (__classPrivateFieldGet(this, _MatimoInstance_policyFile, "f")) {
992
+ // Re-read the original policy file
993
+ newPolicy = loadPolicyFromFile(__classPrivateFieldGet(this, _MatimoInstance_policyFile, "f"));
994
+ }
995
+ else if (__classPrivateFieldGet(this, _MatimoInstance_policy, "f") && 'updateConfig' in __classPrivateFieldGet(this, _MatimoInstance_policy, "f")) {
996
+ // No config provided and no file — nothing to reload
997
+ this.logger.warn('reloadPolicy: no config or file provided, nothing to reload');
998
+ return {
999
+ loaded: 0,
1000
+ removed: 0,
1001
+ revalidated: 0,
1002
+ rejected: [],
1003
+ };
1004
+ }
1005
+ else {
1006
+ this.logger.warn('reloadPolicy: no policy engine to reload');
1007
+ return {
1008
+ loaded: 0,
1009
+ removed: 0,
1010
+ revalidated: 0,
1011
+ rejected: [],
1012
+ };
1013
+ }
1014
+ // Atomic swap: replace the policy engine reference
1015
+ __classPrivateFieldSet(this, _MatimoInstance_policy, newPolicy, "f");
1016
+ Object.freeze(__classPrivateFieldGet(this, _MatimoInstance_policy, "f"));
1017
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
1018
+ type: 'policy:reloaded',
1019
+ timestamp: new Date().toISOString(),
1020
+ });
1021
+ this.logger.info('Policy engine reloaded');
1022
+ // Re-validate all tools against the new policy
1023
+ return this.reloadTools();
1024
+ }
398
1025
  }
1026
+ _a = MatimoInstance, _MatimoInstance_policy = new WeakMap(), _MatimoInstance_integrityTracker = new WeakMap(), _MatimoInstance_approvalManifest = new WeakMap(), _MatimoInstance_onEvent = new WeakMap(), _MatimoInstance_hitlCallback = new WeakMap(), _MatimoInstance_hitlTimeoutMs = new WeakMap(), _MatimoInstance_trustedPaths = new WeakMap(), _MatimoInstance_untrustedPaths = new WeakMap(), _MatimoInstance_policyFile = new WeakMap(), _MatimoInstance_instances = new WeakSet(), _MatimoInstance_emitEvent = function _MatimoInstance_emitEvent(event) {
1027
+ if (__classPrivateFieldGet(this, _MatimoInstance_onEvent, "f")) {
1028
+ try {
1029
+ __classPrivateFieldGet(this, _MatimoInstance_onEvent, "f").call(this, event);
1030
+ }
1031
+ catch {
1032
+ // Never let event handler errors break SDK execution
1033
+ }
1034
+ }
1035
+ }, _MatimoInstance_resolveHITL =
1036
+ /**
1037
+ * Resolve a quarantined tool via HITL callback or approval manifest.
1038
+ *
1039
+ * Resolution order:
1040
+ * 1. Check approval manifest — if already approved, allow.
1041
+ * 2. Invoke HITL callback — if set, ask the human.
1042
+ * 3. If no callback, reject by default (safe fail-closed).
1043
+ */
1044
+ async function _MatimoInstance_resolveHITL(tool, decision, context) {
1045
+ // 1. Check approval manifest — previously approved tools pass through
1046
+ if (__classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f")) {
1047
+ const yamlHash = __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").getHash(tool.name);
1048
+ if (yamlHash && __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").isApproved(tool.name, yamlHash)) {
1049
+ return true;
1050
+ }
1051
+ }
1052
+ // 2. Invoke HITL callback
1053
+ if (__classPrivateFieldGet(this, _MatimoInstance_hitlCallback, "f")) {
1054
+ __classPrivateFieldGet(this, _MatimoInstance_instances, "m", _MatimoInstance_emitEvent).call(this, {
1055
+ type: 'tool:quarantined',
1056
+ toolName: tool.name,
1057
+ riskLevel: decision.riskLevel,
1058
+ reason: decision.reason,
1059
+ environment: context.environment,
1060
+ timestamp: new Date().toISOString(),
1061
+ });
1062
+ const callbackPromise = __classPrivateFieldGet(this, _MatimoInstance_hitlCallback, "f").call(this, {
1063
+ toolName: tool.name,
1064
+ riskLevel: decision.riskLevel,
1065
+ reason: decision.reason,
1066
+ environment: context.environment,
1067
+ agentId: context.agentId,
1068
+ toolDefinition: tool,
1069
+ });
1070
+ let approved;
1071
+ if (__classPrivateFieldGet(this, _MatimoInstance_hitlTimeoutMs, "f") !== null) {
1072
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(_a.HITL_TIMEOUT_SENTINEL), __classPrivateFieldGet(this, _MatimoInstance_hitlTimeoutMs, "f")));
1073
+ const result = await Promise.race([callbackPromise, timeoutPromise]);
1074
+ if (result === _a.HITL_TIMEOUT_SENTINEL) {
1075
+ this.logger.warn(`HITL callback timed out after ${__classPrivateFieldGet(this, _MatimoInstance_hitlTimeoutMs, "f")}ms for tool '${tool.name}' — auto-rejecting`);
1076
+ approved = false;
1077
+ }
1078
+ else {
1079
+ approved = result;
1080
+ }
1081
+ }
1082
+ else {
1083
+ approved = await callbackPromise;
1084
+ }
1085
+ // If approved, record in manifest for future calls
1086
+ if (approved && __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f")) {
1087
+ const yamlHash = __classPrivateFieldGet(this, _MatimoInstance_integrityTracker, "f").getHash(tool.name) ??
1088
+ __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").computeHash(JSON.stringify(tool));
1089
+ __classPrivateFieldGet(this, _MatimoInstance_approvalManifest, "f").approve(tool.name, yamlHash);
1090
+ }
1091
+ return approved;
1092
+ }
1093
+ // 3. No callback — fail closed
1094
+ return false;
1095
+ };
1096
+ // Sentinel value used to distinguish HITL timeout from explicit rejection
1097
+ MatimoInstance.HITL_TIMEOUT_SENTINEL = Symbol('HITL_TIMEOUT');
399
1098
  /**
400
1099
  * Matimo namespace - Entry point for the SDK
401
1100
  */