@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.
- package/README.md +341 -14
- 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 +41 -10
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +40 -4
- 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/tool-loader.d.ts +3 -1
- package/dist/core/tool-loader.d.ts.map +1 -1
- package/dist/core/tool-loader.js +33 -10
- package/dist/core/tool-loader.js.map +1 -1
- package/dist/core/types.d.ts +203 -6
- package/dist/core/types.d.ts.map +1 -1
- package/dist/encodings/parameter-encoding.d.ts +1 -1
- package/dist/encodings/parameter-encoding.d.ts.map +1 -1
- package/dist/encodings/parameter-encoding.js +9 -4
- package/dist/encodings/parameter-encoding.js.map +1 -1
- package/dist/errors/matimo-error.d.ts +11 -2
- package/dist/errors/matimo-error.d.ts.map +1 -1
- package/dist/errors/matimo-error.js +25 -1
- 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 +29 -5
- 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 +44 -24
- package/dist/executors/function-executor.js.map +1 -1
- package/dist/executors/http-executor.d.ts +79 -4
- package/dist/executors/http-executor.d.ts.map +1 -1
- package/dist/executors/http-executor.js +232 -28
- package/dist/executors/http-executor.js.map +1 -1
- package/dist/index.d.ts +25 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -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 +71 -4
- package/dist/integrations/langchain.js.map +1 -1
- package/dist/logging/logger.d.ts +8 -2
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.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 +230 -18
- package/dist/matimo-instance.d.ts.map +1 -1
- package/dist/matimo-instance.js +739 -40
- package/dist/matimo-instance.js.map +1 -1
- package/dist/mcp/index.d.ts +18 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +24 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +141 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -0
- package/dist/mcp/mcp-server.js +754 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/secrets/aws-resolver.d.ts +41 -0
- package/dist/mcp/secrets/aws-resolver.d.ts.map +1 -0
- package/dist/mcp/secrets/aws-resolver.js +141 -0
- package/dist/mcp/secrets/aws-resolver.js.map +1 -0
- package/dist/mcp/secrets/dotenv-resolver.d.ts +23 -0
- package/dist/mcp/secrets/dotenv-resolver.d.ts.map +1 -0
- package/dist/mcp/secrets/dotenv-resolver.js +94 -0
- package/dist/mcp/secrets/dotenv-resolver.js.map +1 -0
- package/dist/mcp/secrets/env-resolver.d.ts +14 -0
- package/dist/mcp/secrets/env-resolver.d.ts.map +1 -0
- package/dist/mcp/secrets/env-resolver.js +27 -0
- package/dist/mcp/secrets/env-resolver.js.map +1 -0
- package/dist/mcp/secrets/index.d.ts +14 -0
- package/dist/mcp/secrets/index.d.ts.map +1 -0
- package/dist/mcp/secrets/index.js +13 -0
- package/dist/mcp/secrets/index.js.map +1 -0
- package/dist/mcp/secrets/resolver-chain.d.ts +34 -0
- package/dist/mcp/secrets/resolver-chain.d.ts.map +1 -0
- package/dist/mcp/secrets/resolver-chain.js +141 -0
- package/dist/mcp/secrets/resolver-chain.js.map +1 -0
- package/dist/mcp/secrets/types.d.ts +73 -0
- package/dist/mcp/secrets/types.d.ts.map +1 -0
- package/dist/mcp/secrets/types.js +8 -0
- package/dist/mcp/secrets/types.js.map +1 -0
- package/dist/mcp/secrets/vault-resolver.d.ts +43 -0
- package/dist/mcp/secrets/vault-resolver.d.ts.map +1 -0
- package/dist/mcp/secrets/vault-resolver.js +127 -0
- package/dist/mcp/secrets/vault-resolver.js.map +1 -0
- package/dist/mcp/tool-converter.d.ts +40 -0
- package/dist/mcp/tool-converter.d.ts.map +1 -0
- package/dist/mcp/tool-converter.js +185 -0
- package/dist/mcp/tool-converter.js.map +1 -0
- package/dist/policy/approval-manifest.d.ts +76 -0
- package/dist/policy/approval-manifest.d.ts.map +1 -0
- package/dist/policy/approval-manifest.js +197 -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 +156 -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 +47 -0
- package/dist/policy/risk-classifier.js.map +1 -0
- package/dist/policy/types.d.ts +131 -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 +22 -6
- 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/definition.yaml +36 -0
- package/tools/matimo_get_tool/matimo_get_tool.ts +56 -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_search_tools/definition.yaml +32 -0
- package/tools/matimo_search_tools/matimo_search_tools.ts +82 -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/read/read.ts +0 -2
- package/tools/shared/skill-validation.ts +335 -0
- package/LICENSE +0 -21
package/dist/matimo-instance.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
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
|
-
*
|
|
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) {
|
|
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
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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
|
*/
|