@matimo/core 0.1.0-alpha.12 → 0.1.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -8
- package/dist/approval/approval-handler.d.ts +5 -1
- package/dist/approval/approval-handler.d.ts.map +1 -1
- package/dist/approval/approval-handler.js +6 -0
- package/dist/approval/approval-handler.js.map +1 -1
- package/dist/core/schema.d.ts +29 -8
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +10 -3
- package/dist/core/schema.js.map +1 -1
- package/dist/core/skill-content-parser.d.ts +91 -0
- package/dist/core/skill-content-parser.d.ts.map +1 -0
- package/dist/core/skill-content-parser.js +248 -0
- package/dist/core/skill-content-parser.js.map +1 -0
- package/dist/core/skill-loader.d.ts +46 -0
- package/dist/core/skill-loader.d.ts.map +1 -0
- package/dist/core/skill-loader.js +310 -0
- package/dist/core/skill-loader.js.map +1 -0
- package/dist/core/skill-registry.d.ts +131 -0
- package/dist/core/skill-registry.d.ts.map +1 -0
- package/dist/core/skill-registry.js +316 -0
- package/dist/core/skill-registry.js.map +1 -0
- package/dist/core/tfidf-embedding.d.ts +45 -0
- package/dist/core/tfidf-embedding.d.ts.map +1 -0
- package/dist/core/tfidf-embedding.js +199 -0
- package/dist/core/tfidf-embedding.js.map +1 -0
- package/dist/core/types.d.ts +192 -6
- package/dist/core/types.d.ts.map +1 -1
- package/dist/encodings/parameter-encoding.d.ts.map +1 -1
- package/dist/encodings/parameter-encoding.js +6 -2
- package/dist/encodings/parameter-encoding.js.map +1 -1
- package/dist/errors/matimo-error.d.ts +3 -1
- package/dist/errors/matimo-error.d.ts.map +1 -1
- package/dist/errors/matimo-error.js +2 -0
- package/dist/errors/matimo-error.js.map +1 -1
- package/dist/executors/command-executor.d.ts +9 -2
- package/dist/executors/command-executor.d.ts.map +1 -1
- package/dist/executors/command-executor.js +14 -2
- package/dist/executors/command-executor.js.map +1 -1
- package/dist/executors/function-executor.d.ts +10 -3
- package/dist/executors/function-executor.d.ts.map +1 -1
- package/dist/executors/function-executor.js +11 -4
- package/dist/executors/function-executor.js.map +1 -1
- package/dist/executors/http-executor.d.ts +16 -2
- package/dist/executors/http-executor.d.ts.map +1 -1
- package/dist/executors/http-executor.js +22 -6
- package/dist/executors/http-executor.js.map +1 -1
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/langchain.d.ts +55 -0
- package/dist/integrations/langchain.d.ts.map +1 -1
- package/dist/integrations/langchain.js +66 -0
- package/dist/integrations/langchain.js.map +1 -1
- package/dist/logging/winston-logger.d.ts.map +1 -1
- package/dist/logging/winston-logger.js +9 -1
- package/dist/logging/winston-logger.js.map +1 -1
- package/dist/matimo-instance.d.ts +210 -18
- package/dist/matimo-instance.d.ts.map +1 -1
- package/dist/matimo-instance.js +704 -40
- package/dist/matimo-instance.js.map +1 -1
- package/dist/mcp/mcp-server.d.ts +23 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +119 -8
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/tool-converter.d.ts.map +1 -1
- package/dist/mcp/tool-converter.js +10 -1
- package/dist/mcp/tool-converter.js.map +1 -1
- package/dist/policy/approval-manifest.d.ts +74 -0
- package/dist/policy/approval-manifest.d.ts.map +1 -0
- package/dist/policy/approval-manifest.js +178 -0
- package/dist/policy/approval-manifest.js.map +1 -0
- package/dist/policy/content-validator.d.ts +19 -0
- package/dist/policy/content-validator.d.ts.map +1 -0
- package/dist/policy/content-validator.js +196 -0
- package/dist/policy/content-validator.js.map +1 -0
- package/dist/policy/default-policy.d.ts +46 -0
- package/dist/policy/default-policy.d.ts.map +1 -0
- package/dist/policy/default-policy.js +241 -0
- package/dist/policy/default-policy.js.map +1 -0
- package/dist/policy/events.d.ts +71 -0
- package/dist/policy/events.d.ts.map +1 -0
- package/dist/policy/events.js +8 -0
- package/dist/policy/events.js.map +1 -0
- package/dist/policy/index.d.ts +13 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +9 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/integrity-tracker.d.ts +62 -0
- package/dist/policy/integrity-tracker.d.ts.map +1 -0
- package/dist/policy/integrity-tracker.js +79 -0
- package/dist/policy/integrity-tracker.js.map +1 -0
- package/dist/policy/policy-loader.d.ts +58 -0
- package/dist/policy/policy-loader.d.ts.map +1 -0
- package/dist/policy/policy-loader.js +153 -0
- package/dist/policy/policy-loader.js.map +1 -0
- package/dist/policy/risk-classifier.d.ts +18 -0
- package/dist/policy/risk-classifier.d.ts.map +1 -0
- package/dist/policy/risk-classifier.js +43 -0
- package/dist/policy/risk-classifier.js.map +1 -0
- package/dist/policy/types.d.ts +126 -0
- package/dist/policy/types.d.ts.map +1 -0
- package/dist/policy/types.js +8 -0
- package/dist/policy/types.js.map +1 -0
- package/package.json +2 -2
- package/tools/matimo_approve_tool/definition.yaml +36 -0
- package/tools/matimo_approve_tool/matimo_approve_tool.ts +90 -0
- package/tools/matimo_create_skill/definition.yaml +46 -0
- package/tools/matimo_create_skill/matimo_create_skill.ts +75 -0
- package/tools/matimo_create_tool/definition.yaml +48 -0
- package/tools/matimo_create_tool/matimo_create_tool.ts +137 -0
- package/tools/matimo_get_skill/definition.yaml +60 -0
- package/tools/matimo_get_skill/matimo_get_skill.ts +182 -0
- package/tools/matimo_get_tool_status/definition.yaml +42 -0
- package/tools/matimo_get_tool_status/matimo_get_tool_status.ts +101 -0
- package/tools/matimo_list_skills/definition.yaml +52 -0
- package/tools/matimo_list_skills/matimo_list_skills.ts +138 -0
- package/tools/matimo_list_user_tools/definition.yaml +32 -0
- package/tools/matimo_list_user_tools/matimo_list_user_tools.ts +74 -0
- package/tools/matimo_reload_tools/definition.yaml +35 -0
- package/tools/matimo_reload_tools/matimo_reload_tools.ts +29 -0
- package/tools/matimo_validate_skill/definition.yaml +43 -0
- package/tools/matimo_validate_skill/matimo_validate_skill.ts +137 -0
- package/tools/matimo_validate_tool/definition.yaml +34 -0
- package/tools/matimo_validate_tool/matimo_validate_tool.ts +168 -0
- package/tools/shared/skill-validation.ts +335 -0
package/dist/matimo-instance.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -121,12 +251,19 @@ export class MatimoInstance {
|
|
|
121
251
|
return this.logger;
|
|
122
252
|
}
|
|
123
253
|
/**
|
|
124
|
-
* Execute a tool by name with parameters
|
|
254
|
+
* Execute a tool by name with parameters.
|
|
255
|
+
*
|
|
125
256
|
* @param toolName - Name of the tool to execute
|
|
126
257
|
* @param params - Tool parameters
|
|
258
|
+
* @param options - Optional execution options
|
|
259
|
+
* @param options.timeout - Execution timeout in milliseconds
|
|
260
|
+
* @param options.credentials - Per-call credential overrides (multi-tenant support).
|
|
261
|
+
* Keys must match the env-var names the tool references (e.g. `SLACK_BOT_TOKEN`).
|
|
262
|
+
* When provided, they take precedence over `process.env` for that single call.
|
|
263
|
+
* Values are never logged and held in memory only for the duration of the call.
|
|
127
264
|
* @returns Tool execution result
|
|
128
265
|
*/
|
|
129
|
-
async execute(toolName, params) {
|
|
266
|
+
async execute(toolName, params, options) {
|
|
130
267
|
const tool = this.registry.get(toolName);
|
|
131
268
|
if (!tool) {
|
|
132
269
|
const availableTools = this.registry.getAll().map((t) => t.name);
|
|
@@ -144,6 +281,38 @@ export class MatimoInstance {
|
|
|
144
281
|
paramCount: Object.keys(params).length,
|
|
145
282
|
});
|
|
146
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
|
+
}
|
|
147
316
|
// Simple approval flow:
|
|
148
317
|
// 1. Check if tool requires approval (from YAML or keyword detection)
|
|
149
318
|
// 2. Check if pre-approved via env vars
|
|
@@ -171,7 +340,12 @@ export class MatimoInstance {
|
|
|
171
340
|
scanContent = parts.join(' ');
|
|
172
341
|
}
|
|
173
342
|
const requiresApproval = this.approvalHandler.requiresApproval(tool.requires_approval, scanContent);
|
|
174
|
-
if (
|
|
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) {
|
|
175
349
|
this.logger.debug(`Approval required for: ${toolName}`, { toolName });
|
|
176
350
|
await this.approvalHandler.requestApproval({
|
|
177
351
|
toolName,
|
|
@@ -180,10 +354,42 @@ export class MatimoInstance {
|
|
|
180
354
|
});
|
|
181
355
|
this.logger.info(`Destructive operation approved: ${toolName}`, { toolName });
|
|
182
356
|
}
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
357
|
+
const credentials = options?.credentials;
|
|
358
|
+
const timeoutOverride = options?.timeout;
|
|
359
|
+
// Auto-inject authentication parameters. When per-call credentials are
|
|
360
|
+
// supplied they take precedence over process.env (multi-tenant support).
|
|
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);
|
|
365
|
+
// Apply per-call timeout override if provided. Create a shallow copy so the
|
|
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
|
+
}
|
|
388
|
+
const effectiveTool = timeoutOverride !== undefined
|
|
389
|
+
? { ...tool, execution: { ...tool.execution, timeout: timeoutOverride } }
|
|
390
|
+
: tool;
|
|
391
|
+
const executor = this.getExecutor(effectiveTool);
|
|
392
|
+
const result = await executor.execute(effectiveTool, finalParams, credentials);
|
|
187
393
|
this.logger.debug(`Tool executed successfully: ${toolName}`, {
|
|
188
394
|
toolName,
|
|
189
395
|
hasResult: !!result,
|
|
@@ -207,34 +413,187 @@ export class MatimoInstance {
|
|
|
207
413
|
return this.registry.get(toolName);
|
|
208
414
|
}
|
|
209
415
|
/**
|
|
210
|
-
* 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).
|
|
211
418
|
* @returns Array of tool definitions
|
|
212
419
|
*/
|
|
213
|
-
listTools() {
|
|
214
|
-
|
|
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;
|
|
215
426
|
}
|
|
216
427
|
/**
|
|
217
428
|
* Get all available tools (alias for listTools)
|
|
218
429
|
* @returns Array of tool definitions
|
|
219
430
|
*/
|
|
220
|
-
getAllTools() {
|
|
221
|
-
return this.
|
|
431
|
+
getAllTools(context) {
|
|
432
|
+
return this.listTools(context);
|
|
222
433
|
}
|
|
223
434
|
/**
|
|
224
435
|
* Search tools by name or description
|
|
225
436
|
* @param query - Search query
|
|
226
437
|
* @returns Matching tools
|
|
227
438
|
*/
|
|
228
|
-
searchTools(query) {
|
|
229
|
-
|
|
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;
|
|
230
445
|
}
|
|
231
446
|
/**
|
|
232
447
|
* Get tools by tag
|
|
233
448
|
* @param tag - Tag to search for
|
|
234
449
|
* @returns Tools with the given tag
|
|
235
450
|
*/
|
|
236
|
-
getToolsByTag(tag) {
|
|
237
|
-
|
|
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;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Return the credential key names that a tool expects.
|
|
460
|
+
*
|
|
461
|
+
* This lets multi-tenant callers know exactly what to put in `options.credentials`
|
|
462
|
+
* without having to read the tool's YAML definition.
|
|
463
|
+
*
|
|
464
|
+
* The returned strings are the keys you pass to `execute()`:
|
|
465
|
+
* ```typescript
|
|
466
|
+
* const keys = matimo.getRequiredCredentials('slack-send-message');
|
|
467
|
+
* // → ['SLACK_BOT_TOKEN']
|
|
468
|
+
*
|
|
469
|
+
* // Then collect from your secrets store:
|
|
470
|
+
* const credentials = Object.fromEntries(
|
|
471
|
+
* keys.map(k => [k, tenant.secrets[k]])
|
|
472
|
+
* );
|
|
473
|
+
* await matimo.execute('slack-send-message', params, { credentials });
|
|
474
|
+
* ```
|
|
475
|
+
*
|
|
476
|
+
* @param toolName - Exact tool name
|
|
477
|
+
* @returns Array of credential key names (may be empty if the tool needs no auth)
|
|
478
|
+
* @throws `MatimoError(TOOL_NOT_FOUND)` if the tool doesn't exist
|
|
479
|
+
*/
|
|
480
|
+
getRequiredCredentials(toolName) {
|
|
481
|
+
const tool = this.registry.get(toolName);
|
|
482
|
+
if (!tool) {
|
|
483
|
+
throw new MatimoError(`Tool '${toolName}' not found in registry`, ErrorCode.TOOL_NOT_FOUND, {
|
|
484
|
+
toolName,
|
|
485
|
+
availableTools: this.registry.getAll().map((t) => t.name),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const authPatterns = [
|
|
489
|
+
'token',
|
|
490
|
+
'key',
|
|
491
|
+
'secret',
|
|
492
|
+
'password',
|
|
493
|
+
'credential',
|
|
494
|
+
'auth',
|
|
495
|
+
'bearer',
|
|
496
|
+
'api_key',
|
|
497
|
+
];
|
|
498
|
+
const referencedParams = this.extractParameterPlaceholders(tool);
|
|
499
|
+
const credentialKeys = [];
|
|
500
|
+
for (const paramName of referencedParams) {
|
|
501
|
+
const lowerName = paramName.toLowerCase();
|
|
502
|
+
if (authPatterns.some((pattern) => lowerName.includes(pattern))) {
|
|
503
|
+
credentialKeys.push(paramName);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Also include basic-auth env var names declared in authentication config
|
|
507
|
+
const auth = tool.authentication;
|
|
508
|
+
if (auth?.type === 'basic') {
|
|
509
|
+
if (auth.username_env && !credentialKeys.includes(auth.username_env)) {
|
|
510
|
+
credentialKeys.push(auth.username_env);
|
|
511
|
+
}
|
|
512
|
+
if (auth.password_env && !credentialKeys.includes(auth.password_env)) {
|
|
513
|
+
credentialKeys.push(auth.password_env);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return credentialKeys;
|
|
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];
|
|
238
597
|
}
|
|
239
598
|
/**
|
|
240
599
|
* Automatically inject parameters from environment variables
|
|
@@ -242,19 +601,15 @@ export class MatimoInstance {
|
|
|
242
601
|
*
|
|
243
602
|
* 1. Scans the execution config for all parameter placeholders
|
|
244
603
|
* 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
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
604
|
+
* 3. If yes, attempts to load from (in order of priority):
|
|
605
|
+
* a. `credentials[paramName]` — per-call override (multi-tenant)
|
|
606
|
+
* b. `credentials[MATIMO_${paramName}]` — prefixed per-call override
|
|
607
|
+
* c. `process.env[MATIMO_${paramName}]` — prefixed env var
|
|
608
|
+
* d. `process.env[paramName]` — direct env var
|
|
249
609
|
*
|
|
250
|
-
*
|
|
251
|
-
|
|
252
|
-
|
|
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) {
|
|
610
|
+
* Credential values are never logged.
|
|
611
|
+
*/
|
|
612
|
+
injectAuthParameters(tool, params, credentials) {
|
|
258
613
|
const result = { ...params };
|
|
259
614
|
// Collect all parameter names referenced in the execution config
|
|
260
615
|
const referencedParams = this.extractParameterPlaceholders(tool);
|
|
@@ -279,21 +634,79 @@ export class MatimoInstance {
|
|
|
279
634
|
const lowerName = paramName.toLowerCase();
|
|
280
635
|
const isAuthParam = authPatterns.some((pattern) => lowerName.includes(pattern));
|
|
281
636
|
if (isAuthParam) {
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
637
|
+
// Lookup order:
|
|
638
|
+
// 1. Per-call credentials (multi-tenant override — never touches process.env)
|
|
639
|
+
// 2. Per-call credentials with MATIMO_ prefix
|
|
640
|
+
// 3. Environment variable with MATIMO_ prefix
|
|
641
|
+
// 4. Environment variable with the exact param name
|
|
642
|
+
let resolvedValue;
|
|
643
|
+
if (credentials) {
|
|
644
|
+
resolvedValue = credentials[paramName] ?? credentials[`MATIMO_${paramName}`];
|
|
645
|
+
}
|
|
646
|
+
if (!resolvedValue) {
|
|
647
|
+
resolvedValue = process.env[`MATIMO_${paramName}`] ?? process.env[paramName];
|
|
288
648
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
result[paramName] = envValue;
|
|
649
|
+
if (resolvedValue) {
|
|
650
|
+
result[paramName] = resolvedValue;
|
|
292
651
|
}
|
|
293
652
|
}
|
|
294
653
|
}
|
|
295
654
|
return result;
|
|
296
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
|
+
}
|
|
297
710
|
/**
|
|
298
711
|
* Extract all parameter placeholders from execution config
|
|
299
712
|
* Scans headers, body, URL, and query_params for {paramName} patterns
|
|
@@ -395,7 +808,258 @@ export class MatimoInstance {
|
|
|
395
808
|
throw new MatimoError(`Unsupported execution type: ${executionType}`, ErrorCode.EXECUTION_FAILED, { executionType });
|
|
396
809
|
}
|
|
397
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
|
+
}
|
|
398
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
|
+
};
|
|
399
1063
|
/**
|
|
400
1064
|
* Matimo namespace - Entry point for the SDK
|
|
401
1065
|
*/
|