toolfunnel 0.4.0 → 0.6.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 +102 -213
- package/bin/toolfunnel.js +372 -147
- package/docs/KNOWN_BUGS.md +99 -225
- package/docs/MANUAL.pdf +0 -0
- package/docs/design.md +70 -0
- package/docs/packaging.md +115 -115
- package/hooks/hooks.manifest.json +27 -27
- package/hooks/scripts/example-audit.js +21 -21
- package/hooks/scripts/example-deny-dangerous.js +31 -31
- package/mcp/expose.example.json +30 -30
- package/mcp/servers/mock-modern/server.js +147 -0
- package/mcp/servers/mock-upstream/server.js +192 -12
- package/package.json +1 -1
- package/src/auth/config.js +174 -174
- package/src/auth/install.js +181 -181
- package/src/auth/resource-server.js +344 -344
- package/src/core/config-home.js +134 -134
- package/src/core/events.js +105 -105
- package/src/core/hook-engine.js +275 -275
- package/src/core/hook-loader.js +791 -791
- package/src/core/hook-runner.js +346 -346
- package/src/core/logger.js +171 -171
- package/src/core/matcher.js +51 -51
- package/src/core/metrics.js +75 -75
- package/src/core/requires.js +138 -138
- package/src/core/server-config.js +98 -77
- package/src/core/tool-registry.js +180 -180
- package/src/extend/add-hook.md +152 -142
- package/src/extend/add-mcp.md +122 -121
- package/src/extend/configure.md +87 -0
- package/src/extend/create-tool.md +168 -152
- package/src/extend/howto.js +102 -98
- package/src/extend/package.md +187 -187
- package/src/extend/wrap.md +99 -0
- package/src/mcp/aggregator.js +1246 -807
- package/src/mcp/expose-store.js +460 -430
- package/src/mcp/gated-run.js +201 -194
- package/src/mcp/http-transport.js +1311 -981
- package/src/mcp/mcp-client.js +907 -530
- package/src/mcp/modern.js +372 -0
- package/src/mcp/protocol.js +468 -466
- package/src/mcp/server.js +2688 -1322
- package/src/packages/packager.js +715 -715
- package/src/tools/drift.js +138 -138
- package/src/tools/registry.js +513 -511
- package/src/tools/tool-state.js +170 -104
- package/src/ui/public/app.js +1174 -1055
- package/src/ui/public/index.html +286 -249
- package/src/ui/public/styles.css +535 -535
- package/src/ui/server.js +1348 -1190
- package/tools/scripts/base64.js +139 -139
- package/tools/scripts/danger.js +61 -61
- package/tools/scripts/echo.js +39 -39
- package/tools/scripts/hash.js +101 -101
- package/tools/scripts/json.js +109 -109
- package/tools/scripts/text-stats.js +131 -131
- package/tools/scripts/tf-env.js +43 -43
- package/tools/scripts/tf-hook-add.js +151 -151
- package/tools/scripts/tf-hook-set.js +112 -112
- package/tools/scripts/tf-list.js +192 -192
- package/tools/scripts/tf-log.js +86 -86
- package/tools/scripts/tf-mcp-add.js +117 -117
- package/tools/scripts/tf-mcp-set.js +121 -102
- package/tools/scripts/tf-pack.js +237 -237
- package/tools/scripts/tf-tool-add.js +134 -129
- package/tools/scripts/tf-tool-set.js +142 -142
- package/tools/scripts/tf-wrap.js +103 -0
- package/tools/scripts/uuid.js +102 -102
- package/tools/tools.register.json +227 -216
- package/docs/admin-ui.md +0 -404
- package/docs/configuration.md +0 -402
- package/docs/hooks-and-gating.md +0 -428
- package/docs/logging.md +0 -223
- package/docs/management.md +0 -429
- package/docs/oauth.md +0 -103
- package/docs/releasing.md +0 -31
- package/docs/setup.md +0 -352
- package/docs/tools-and-upstreams.md +0 -469
package/src/mcp/gated-run.js
CHANGED
|
@@ -1,194 +1,201 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* gated-run.js
|
|
5
|
-
*
|
|
6
|
-
* Contract: the architecture contract §2 (the `toolfunnel_run_tool` path) and §9 (the tested invariant).
|
|
7
|
-
* Hook contract: HOOK_ENGINE.md §1 (PreToolUse can block, PostToolUse cannot un-run) and §6
|
|
8
|
-
* (HookEngine.fire returns { injected, blocked, reason, stopLoop, results }; the engine has
|
|
9
|
-
* ALREADY normalised exit-2 / hookSpecificOutput.permissionDecision:"deny" into { blocked, reason }).
|
|
10
|
-
*
|
|
11
|
-
* This is the single place where "run a tool" becomes "fire PreToolUse
|
|
12
|
-
* fire PostToolUse". Every powerful run-path (toolfunnel_run_tool, registry.run) funnels through here so
|
|
13
|
-
* the gate is impossible to bypass. The LOAD-BEARING INVARIANT, proven by test:
|
|
14
|
-
*
|
|
15
|
-
* A PreToolUse deny MUST prevent execute() from ever being called.
|
|
16
|
-
*
|
|
17
|
-
* Design rules:
|
|
18
|
-
* - PURE: `engine` and `execute` are INJECTED. No transport, no SDK, no host, no fs.
|
|
19
|
-
* Unit-testable under `node --test` with a fake engine + a spy execute().
|
|
20
|
-
* - NEVER throws out of gatedRun. A thrown execute() is captured as { ok:false, error }.
|
|
21
|
-
* A misbehaving engine.fire() (rejection or junk return) is treated conservatively.
|
|
22
|
-
* - PostToolUse is advisory (HOOK_ENGINE.md §1: a PostToolUse block cannot un-run the tool),
|
|
23
|
-
* so we fire it for feedback but never let it flip ok
|
|
24
|
-
*
|
|
25
|
-
* Return shape (always one of these, never an exception):
|
|
26
|
-
* blocked by PreToolUse : { ok:false, blocked:true, reason, output:null }
|
|
27
|
-
* execute threw : { ok:false, blocked:false, error, output:null }
|
|
28
|
-
* success : { ok:true, blocked:false, output }
|
|
29
|
-
*
|
|
30
|
-
* CommonJS only. Node built-ins only. No new dependencies.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
// Activity log. Self-gating (no-op unless explicitly enabled) and contracted never to
|
|
34
|
-
// throw, so wiring it here cannot alter gate behaviour or break the run path.
|
|
35
|
-
const logger = require('../core/logger');
|
|
36
|
-
|
|
37
|
-
const EVENTS = {
|
|
38
|
-
PRE: 'PreToolUse',
|
|
39
|
-
POST: 'PostToolUse',
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Fire one lifecycle event through the injected engine, defensively.
|
|
44
|
-
*
|
|
45
|
-
* The HookEngine contract (HOOK_ENGINE.md §6) says fire() never rejects, but gatedRun is the
|
|
46
|
-
* safety crux: we must not assume a perfectly-behaved engine. If fire() rejects or returns a
|
|
47
|
-
* non-object, we fail CLOSED on the gate (PreToolUse)
|
|
48
|
-
* "allowed?" must not be read as "allowed". PostToolUse is advisory, so a failure there is benign.
|
|
49
|
-
*
|
|
50
|
-
* @param {object} engine an object exposing async fire(event, ctx, extra)
|
|
51
|
-
* @param {string} event one of EVENTS
|
|
52
|
-
* @param {object} ctx common hook context (session_id, transcript_path, cwd,
|
|
53
|
-
* @param {object} extra event-specific fields (per HOOK_ENGINE.md §2 table)
|
|
54
|
-
* @returns {Promise<{ injected:string, blocked:boolean, reason:(string|null),
|
|
55
|
-
* stopLoop:boolean, results:object[] }>}
|
|
56
|
-
*/
|
|
57
|
-
async function fireSafely(engine, event, ctx, extra) {
|
|
58
|
-
try {
|
|
59
|
-
const res = await engine.fire(event, ctx, extra);
|
|
60
|
-
// The engine already normalises blocking to { blocked, reason }. Defensively coerce a
|
|
61
|
-
// junk/undefined return into a well-shaped result so callers can read fields safely.
|
|
62
|
-
if (res && typeof res === 'object') {
|
|
63
|
-
return {
|
|
64
|
-
injected: typeof res.injected === 'string' ? res.injected : '',
|
|
65
|
-
blocked: res.blocked === true,
|
|
66
|
-
reason: res.reason != null ? res.reason : null,
|
|
67
|
-
stopLoop: res.stopLoop === true,
|
|
68
|
-
results: Array.isArray(res.results) ? res.results : [],
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
// Non-object return from fire(): per-event. PreToolUse fails CLOSED
|
|
72
|
-
// returns junk cannot be read as "allowed". PostToolUse is advisory
|
|
73
|
-
if (event === EVENTS.PRE) {
|
|
74
|
-
return {
|
|
75
|
-
injected: '',
|
|
76
|
-
blocked: true,
|
|
77
|
-
reason: 'hook engine returned a non-object for ' + event + '
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* @param {object} params
|
|
110
|
-
* @param {object}
|
|
111
|
-
* @param {
|
|
112
|
-
* @param {
|
|
113
|
-
* @param {
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gated-run.js - the SAFETY CRUX of the gateway.
|
|
5
|
+
*
|
|
6
|
+
* Contract: the architecture contract §2 (the `toolfunnel_run_tool` path) and §9 (the tested invariant).
|
|
7
|
+
* Hook contract: HOOK_ENGINE.md §1 (PreToolUse can block, PostToolUse cannot un-run) and §6
|
|
8
|
+
* (HookEngine.fire returns { injected, blocked, reason, stopLoop, results }; the engine has
|
|
9
|
+
* ALREADY normalised exit-2 / hookSpecificOutput.permissionDecision:"deny" into { blocked, reason }).
|
|
10
|
+
*
|
|
11
|
+
* This is the single place where "run a tool" becomes "fire PreToolUse -> maybe execute ->
|
|
12
|
+
* fire PostToolUse". Every powerful run-path (toolfunnel_run_tool, registry.run) funnels through here so
|
|
13
|
+
* the gate is impossible to bypass. The LOAD-BEARING INVARIANT, proven by test:
|
|
14
|
+
*
|
|
15
|
+
* A PreToolUse deny MUST prevent execute() from ever being called.
|
|
16
|
+
*
|
|
17
|
+
* Design rules:
|
|
18
|
+
* - PURE: `engine` and `execute` are INJECTED. No transport, no SDK, no host, no fs.
|
|
19
|
+
* Unit-testable under `node --test` with a fake engine + a spy execute().
|
|
20
|
+
* - NEVER throws out of gatedRun. A thrown execute() is captured as { ok:false, error }.
|
|
21
|
+
* A misbehaving engine.fire() (rejection or junk return) is treated conservatively.
|
|
22
|
+
* - PostToolUse is advisory (HOOK_ENGINE.md §1: a PostToolUse block cannot un-run the tool),
|
|
23
|
+
* so we fire it for feedback but never let it flip ok->false or throw the path.
|
|
24
|
+
*
|
|
25
|
+
* Return shape (always one of these, never an exception):
|
|
26
|
+
* blocked by PreToolUse : { ok:false, blocked:true, reason, output:null }
|
|
27
|
+
* execute threw : { ok:false, blocked:false, error, output:null }
|
|
28
|
+
* success : { ok:true, blocked:false, output }
|
|
29
|
+
*
|
|
30
|
+
* CommonJS only. Node built-ins only. No new dependencies.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// Activity log. Self-gating (no-op unless explicitly enabled) and contracted never to
|
|
34
|
+
// throw, so wiring it here cannot alter gate behaviour or break the run path.
|
|
35
|
+
const logger = require('../core/logger');
|
|
36
|
+
|
|
37
|
+
const EVENTS = {
|
|
38
|
+
PRE: 'PreToolUse',
|
|
39
|
+
POST: 'PostToolUse',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Fire one lifecycle event through the injected engine, defensively.
|
|
44
|
+
*
|
|
45
|
+
* The HookEngine contract (HOOK_ENGINE.md §6) says fire() never rejects, but gatedRun is the
|
|
46
|
+
* safety crux: we must not assume a perfectly-behaved engine. If fire() rejects or returns a
|
|
47
|
+
* non-object, we fail CLOSED on the gate (PreToolUse) - an engine we cannot trust to answer
|
|
48
|
+
* "allowed?" must not be read as "allowed". PostToolUse is advisory, so a failure there is benign.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} engine an object exposing async fire(event, ctx, extra)
|
|
51
|
+
* @param {string} event one of EVENTS
|
|
52
|
+
* @param {object} ctx common hook context (session_id, transcript_path, cwd, ...)
|
|
53
|
+
* @param {object} extra event-specific fields (per HOOK_ENGINE.md §2 table)
|
|
54
|
+
* @returns {Promise<{ injected:string, blocked:boolean, reason:(string|null),
|
|
55
|
+
* stopLoop:boolean, results:object[] }>}
|
|
56
|
+
*/
|
|
57
|
+
async function fireSafely(engine, event, ctx, extra) {
|
|
58
|
+
try {
|
|
59
|
+
const res = await engine.fire(event, ctx, extra);
|
|
60
|
+
// The engine already normalises blocking to { blocked, reason }. Defensively coerce a
|
|
61
|
+
// junk/undefined return into a well-shaped result so callers can read fields safely.
|
|
62
|
+
if (res && typeof res === 'object') {
|
|
63
|
+
return {
|
|
64
|
+
injected: typeof res.injected === 'string' ? res.injected : '',
|
|
65
|
+
blocked: res.blocked === true,
|
|
66
|
+
reason: res.reason != null ? res.reason : null,
|
|
67
|
+
stopLoop: res.stopLoop === true,
|
|
68
|
+
results: Array.isArray(res.results) ? res.results : [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Non-object return from fire(): per-event. PreToolUse fails CLOSED - a gate that
|
|
72
|
+
// returns junk cannot be read as "allowed". PostToolUse is advisory -> benign.
|
|
73
|
+
if (event === EVENTS.PRE) {
|
|
74
|
+
return {
|
|
75
|
+
injected: '',
|
|
76
|
+
blocked: true,
|
|
77
|
+
reason: 'hook engine returned a non-object for ' + event + ' - failing closed',
|
|
78
|
+
internal: true, // wiring failure, NOT operator policy - wrap paths must neutralise it
|
|
79
|
+
stopLoop: false,
|
|
80
|
+
results: [],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { injected: '', blocked: false, reason: null, stopLoop: false, results: [] };
|
|
84
|
+
} catch (err) {
|
|
85
|
+
// fire() should never reject; if it does, fail closed on the GATE and benign on POST.
|
|
86
|
+
const reason =
|
|
87
|
+
'hook engine error during ' + event + ': ' + ((err && err.message) || String(err));
|
|
88
|
+
if (event === EVENTS.PRE) {
|
|
89
|
+
// Fail CLOSED: an engine that cannot answer the gate denies the run.
|
|
90
|
+
return { injected: '', blocked: true, reason, internal: true, stopLoop: false, results: [] };
|
|
91
|
+
}
|
|
92
|
+
// PostToolUse is advisory and cannot un-run the tool - a failure here is benign.
|
|
93
|
+
return { injected: '', blocked: false, reason: null, stopLoop: false, results: [] };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* gatedRun - route a single tool invocation through the host's hook engine.
|
|
99
|
+
*
|
|
100
|
+
* Sequence (the architecture contract §2):
|
|
101
|
+
* 1. fire PreToolUse with { tool_name, tool_input: args }.
|
|
102
|
+
* 2. if blocked -> return { ok:false, blocked:true, reason, output:null } and DO NOT call execute.
|
|
103
|
+
* 3. else await execute(); on throw, capture as error (output stays null).
|
|
104
|
+
* 4. fire PostToolUse with { tool_name, tool_input: args, tool_response: output }.
|
|
105
|
+
* (PostToolUse is advisory - fired for feedback even on the error path so PostToolUse hooks
|
|
106
|
+
* observe what happened; it never flips ok or throws.)
|
|
107
|
+
* 5. return { ok:true, blocked:false, output } | { ok:false, blocked:false, error, output:null }.
|
|
108
|
+
*
|
|
109
|
+
* @param {object} params
|
|
110
|
+
* @param {object} params.engine the host's HookEngine - async fire(event, ctx, extra). INJECTED.
|
|
111
|
+
* @param {object} [params.ctx] common hook context (session_id, transcript_path, cwd, ...).
|
|
112
|
+
* @param {string} params.toolName the tool name (becomes tool_name in the payload).
|
|
113
|
+
* @param {*} [params.args] the tool input (becomes tool_input in the payload).
|
|
114
|
+
* @param {Function} params.execute thunk that performs the actual call (from
|
|
115
|
+
* registry.resolveExecution). `() => any | Promise<any>`. INJECTED.
|
|
116
|
+
* @returns {Promise<{ ok:boolean, blocked:boolean, reason?:(string|null),
|
|
117
|
+
* output:(*|null), error?:Error }>}
|
|
118
|
+
*/
|
|
119
|
+
async function gatedRun(params) {
|
|
120
|
+
const p = params || {};
|
|
121
|
+
const { engine, ctx, toolName, args, execute } = p;
|
|
122
|
+
|
|
123
|
+
// ---- Validate injected dependencies, failing CLOSED (never execute on a bad wiring). ----
|
|
124
|
+
if (!engine || typeof engine.fire !== 'function') {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
blocked: true,
|
|
128
|
+
reason: 'gatedRun: engine with async fire() is required',
|
|
129
|
+
internal: true, // wiring failure, NOT operator policy - wrap paths must neutralise it
|
|
130
|
+
output: null,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (typeof execute !== 'function') {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
blocked: true,
|
|
137
|
+
reason: 'gatedRun: execute thunk (function) is required',
|
|
138
|
+
internal: true,
|
|
139
|
+
output: null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const context = ctx || {};
|
|
144
|
+
|
|
145
|
+
// ---- 1. PreToolUse gate. -------------------------------------------------------------
|
|
146
|
+
// tool_input carries the args verbatim (per HOOK_ENGINE.md §2 PreToolUse row).
|
|
147
|
+
const pre = await fireSafely(engine, EVENTS.PRE, context, {
|
|
148
|
+
tool_name: toolName,
|
|
149
|
+
tool_input: args,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ---- Activity log: record the gate decision (allow|deny). Self-gates on enabled;
|
|
153
|
+
// never throws. Placed before the blocked-return so BOTH outcomes are logged. ---
|
|
154
|
+
logger.log({
|
|
155
|
+
type: 'gate',
|
|
156
|
+
tool: toolName,
|
|
157
|
+
decision: pre.blocked ? 'deny' : 'allow',
|
|
158
|
+
reason: pre.reason,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ---- 2. Blocked -> STOP. The load-bearing invariant: execute() is NOT called. ---------
|
|
162
|
+
if (pre.blocked) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
blocked: true,
|
|
166
|
+
reason: pre.reason,
|
|
167
|
+
// Distinguish a genuine hook DENY (operator policy - its reason may be shown to a wrapped
|
|
168
|
+
// client) from an internal fail-closed (engine wiring/crash - its reason is a gateway tell
|
|
169
|
+
// that must be neutralised on the wrap path).
|
|
170
|
+
internal: pre.internal === true,
|
|
171
|
+
output: null,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---- 3. Allowed -> execute the real call. Capture a throw as `error`. -----------------
|
|
176
|
+
let output = null;
|
|
177
|
+
let error = null;
|
|
178
|
+
try {
|
|
179
|
+
output = await execute();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
// Normalise any thrown value into an Error so the caller always gets an Error instance.
|
|
182
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---- 4. PostToolUse (advisory). Fired even on the error path so hooks see the outcome. -
|
|
186
|
+
// tool_response is the output (null when execute threw). PostToolUse cannot un-run the tool
|
|
187
|
+
// (HOOK_ENGINE.md §1), so its result never flips ok and never throws this path.
|
|
188
|
+
await fireSafely(engine, EVENTS.POST, context, {
|
|
189
|
+
tool_name: toolName,
|
|
190
|
+
tool_input: args,
|
|
191
|
+
tool_response: output,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ---- 5. Final result. ----------------------------------------------------------------
|
|
195
|
+
if (error) {
|
|
196
|
+
return { ok: false, blocked: false, error, output: null };
|
|
197
|
+
}
|
|
198
|
+
return { ok: true, blocked: false, output };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { gatedRun, EVENTS };
|