@sidurijs/hands 1.0.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 +35 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +413 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +407 -0
- package/dist/life-tools.d.ts +9 -0
- package/dist/life-tools.js +216 -0
- package/dist/mcp-integration.test.d.ts +1 -0
- package/dist/mcp-integration.test.js +235 -0
- package/dist/mcp-provider.d.ts +33 -0
- package/dist/mcp-provider.js +131 -0
- package/dist/schema-validator.d.ts +5 -0
- package/dist/schema-validator.js +138 -0
- package/organ-manifest.json +56 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @sidurijs/hands
|
|
2
|
+
|
|
3
|
+
Hands Organ for Siduri: Model Context Protocol (MCP) server integration, tool execution, action lifecycle tracking, and cryptographic policy-driven action enforcement.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Model Context Protocol (MCP) Client**: Connects via Stdio, Server-Sent Events (SSE), or custom transports using `@modelcontextprotocol/sdk`.
|
|
8
|
+
- **Dynamic Tool Discovery**: Queries `tools/list` on connected MCP servers and maps definitions into Siduri's tool registry.
|
|
9
|
+
- **Cryptographic Capability Enforcement**: Verifies HMAC-signed `AuthorizationCapability` tokens before any tool execution can proceed.
|
|
10
|
+
- **Idempotency & Concurrency Locks**: Backed by `ActionStore` to prevent duplicate concurrent or replayed side-effects.
|
|
11
|
+
- **Recursive Schema Validation**: Enforces JSON Schema types and defends against prototype pollution attempts.
|
|
12
|
+
- **Execution Lifecycle Management**: Enforces configurable timeouts and propagates AbortController cancellation signals.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { DefaultHandsOrgan } from '@sidurijs/hands';
|
|
18
|
+
|
|
19
|
+
const hands = new DefaultHandsOrgan({
|
|
20
|
+
defaultTimeoutMs: 15000,
|
|
21
|
+
providers: [
|
|
22
|
+
{
|
|
23
|
+
serverName: 'filesystem',
|
|
24
|
+
command: 'npx',
|
|
25
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir']
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Discovers tools dynamically from configured providers
|
|
31
|
+
const tools = await hands.listTools();
|
|
32
|
+
|
|
33
|
+
// Executes authorized tool intents with policy tokens
|
|
34
|
+
const result = await hands.executeAction(actionIntent, authorizationCapability);
|
|
35
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HandsOrgan, ToolDefinition, ActionIntent, ActionExecutionResult, AuthorizationCapability, ToolExecutionOptions, ActionStore } from '@sidurijs/core';
|
|
2
|
+
import { MCPClientProvider, MCPProviderConfig, MCPToolHandler } from './mcp-provider';
|
|
3
|
+
import { createLifeTools, LifeToolsOptions } from './life-tools';
|
|
4
|
+
export type ToolHandler = MCPToolHandler;
|
|
5
|
+
export { MCPClientProvider, MCPProviderConfig, createLifeTools, LifeToolsOptions };
|
|
6
|
+
export interface DefaultHandsOrganConfig {
|
|
7
|
+
providers?: MCPProviderConfig[];
|
|
8
|
+
defaultTimeoutMs?: number;
|
|
9
|
+
store?: ActionStore;
|
|
10
|
+
secretKey?: string;
|
|
11
|
+
knowledge?: any;
|
|
12
|
+
}
|
|
13
|
+
export declare class DefaultHandsOrgan implements HandsOrgan {
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly toolRegistry;
|
|
16
|
+
private readonly providerTools;
|
|
17
|
+
private readonly mcpProviders;
|
|
18
|
+
private readonly defaultTimeoutMs;
|
|
19
|
+
private readonly store;
|
|
20
|
+
private readonly secretKey;
|
|
21
|
+
private initialized;
|
|
22
|
+
private initializePromise?;
|
|
23
|
+
constructor(config?: DefaultHandsOrganConfig);
|
|
24
|
+
addProvider(providerConfig: MCPProviderConfig): MCPClientProvider;
|
|
25
|
+
getProvider(serverName: string): MCPClientProvider | undefined;
|
|
26
|
+
initialize(): Promise<void>;
|
|
27
|
+
registerTool(handler: ToolHandler, providerId?: string): void;
|
|
28
|
+
unregisterTool(toolIdentifier: string): boolean;
|
|
29
|
+
listTools(): Promise<ToolDefinition[]>;
|
|
30
|
+
findHandler(toolIdentifier: string): ToolHandler | undefined;
|
|
31
|
+
executeAction(action: ActionIntent, authorization: AuthorizationCapability, options?: ToolExecutionOptions): Promise<ActionExecutionResult>;
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare function probeHandsHealth(context: {
|
|
35
|
+
config?: any;
|
|
36
|
+
env?: Record<string, string | undefined>;
|
|
37
|
+
}): {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
message?: string;
|
|
40
|
+
};
|
|
41
|
+
export * from './schema-validator';
|
|
42
|
+
export * from './mcp-provider';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DefaultHandsOrgan = exports.createLifeTools = exports.MCPClientProvider = void 0;
|
|
18
|
+
exports.probeHandsHealth = probeHandsHealth;
|
|
19
|
+
const core_1 = require("@sidurijs/core");
|
|
20
|
+
const schema_validator_1 = require("./schema-validator");
|
|
21
|
+
const mcp_provider_1 = require("./mcp-provider");
|
|
22
|
+
Object.defineProperty(exports, "MCPClientProvider", { enumerable: true, get: function () { return mcp_provider_1.MCPClientProvider; } });
|
|
23
|
+
const life_tools_1 = require("./life-tools");
|
|
24
|
+
Object.defineProperty(exports, "createLifeTools", { enumerable: true, get: function () { return life_tools_1.createLifeTools; } });
|
|
25
|
+
class DefaultHandsOrgan {
|
|
26
|
+
config;
|
|
27
|
+
toolRegistry = new Map();
|
|
28
|
+
providerTools = new Map();
|
|
29
|
+
mcpProviders = new Map();
|
|
30
|
+
defaultTimeoutMs;
|
|
31
|
+
store;
|
|
32
|
+
secretKey;
|
|
33
|
+
initialized = false;
|
|
34
|
+
initializePromise;
|
|
35
|
+
constructor(config = {}) {
|
|
36
|
+
this.config = config;
|
|
37
|
+
this.defaultTimeoutMs = config.defaultTimeoutMs ?? 10_000;
|
|
38
|
+
this.store = config.store ?? new core_1.InMemoryActionStore();
|
|
39
|
+
this.secretKey = (0, core_1.getOrGenerateLocalActionPolicySecret)(config.secretKey);
|
|
40
|
+
if (config.knowledge) {
|
|
41
|
+
const lifeTools = (0, life_tools_1.createLifeTools)(config.knowledge);
|
|
42
|
+
for (const tool of lifeTools) {
|
|
43
|
+
this.registerTool(tool, 'life');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (config.providers) {
|
|
47
|
+
for (const providerConfig of config.providers) {
|
|
48
|
+
this.addProvider(providerConfig);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
addProvider(providerConfig) {
|
|
53
|
+
const provider = new mcp_provider_1.MCPClientProvider(providerConfig);
|
|
54
|
+
this.mcpProviders.set(providerConfig.serverName, provider);
|
|
55
|
+
// Register static tools immediately if supplied
|
|
56
|
+
if (providerConfig.tools) {
|
|
57
|
+
for (const tool of providerConfig.tools) {
|
|
58
|
+
this.registerTool(tool, providerConfig.serverName);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return provider;
|
|
62
|
+
}
|
|
63
|
+
getProvider(serverName) {
|
|
64
|
+
return this.mcpProviders.get(serverName);
|
|
65
|
+
}
|
|
66
|
+
async initialize() {
|
|
67
|
+
if (this.initialized) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (this.initializePromise) {
|
|
71
|
+
return this.initializePromise;
|
|
72
|
+
}
|
|
73
|
+
this.initializePromise = (async () => {
|
|
74
|
+
for (const provider of this.mcpProviders.values()) {
|
|
75
|
+
try {
|
|
76
|
+
const discovered = await provider.discoverTools();
|
|
77
|
+
for (const handler of discovered) {
|
|
78
|
+
this.registerTool(handler, provider.serverName);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
// Log or retain error, allow other providers to initialize
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this.initialized = true;
|
|
86
|
+
})();
|
|
87
|
+
try {
|
|
88
|
+
await this.initializePromise;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
this.initializePromise = undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
registerTool(handler, providerId) {
|
|
95
|
+
const effectiveProvider = providerId || handler.definition.providerId || 'builtin';
|
|
96
|
+
const toolName = handler.definition.name;
|
|
97
|
+
const qualifiedName = `${effectiveProvider}/${toolName}`;
|
|
98
|
+
this.providerTools.set(qualifiedName, handler);
|
|
99
|
+
this.toolRegistry.set(qualifiedName, handler);
|
|
100
|
+
if (!this.toolRegistry.has(toolName)) {
|
|
101
|
+
this.toolRegistry.set(toolName, handler);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const existing = this.toolRegistry.get(toolName);
|
|
105
|
+
if (existing && existing !== handler) {
|
|
106
|
+
this.toolRegistry.delete(toolName);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
unregisterTool(toolIdentifier) {
|
|
111
|
+
const deleted1 = this.toolRegistry.delete(toolIdentifier);
|
|
112
|
+
const deleted2 = this.providerTools.delete(toolIdentifier);
|
|
113
|
+
return deleted1 || deleted2;
|
|
114
|
+
}
|
|
115
|
+
async listTools() {
|
|
116
|
+
if (!this.initialized && this.mcpProviders.size > 0) {
|
|
117
|
+
await this.initialize();
|
|
118
|
+
}
|
|
119
|
+
return Array.from(this.providerTools.values()).map((handler) => handler.definition);
|
|
120
|
+
}
|
|
121
|
+
findHandler(toolIdentifier) {
|
|
122
|
+
return this.toolRegistry.get(toolIdentifier) || this.providerTools.get(toolIdentifier);
|
|
123
|
+
}
|
|
124
|
+
async executeAction(action, authorization, options) {
|
|
125
|
+
const startTime = Date.now();
|
|
126
|
+
const actionId = action?.actionId || 'unknown';
|
|
127
|
+
const toolName = action?.toolName || 'unknown';
|
|
128
|
+
// 1. Mandatory Authorization Capability Verification
|
|
129
|
+
if (!authorization) {
|
|
130
|
+
return {
|
|
131
|
+
actionId,
|
|
132
|
+
executionId: 'unauthorized',
|
|
133
|
+
toolName,
|
|
134
|
+
lifecycle: 'REJECTED',
|
|
135
|
+
success: false,
|
|
136
|
+
error: 'Execution rejected: Missing mandatory AuthorizationCapability from policy engine',
|
|
137
|
+
durationMs: Date.now() - startTime,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (authorization.allowed !== true) {
|
|
141
|
+
return {
|
|
142
|
+
actionId,
|
|
143
|
+
executionId: authorization.executionId || 'unauthorized',
|
|
144
|
+
toolName,
|
|
145
|
+
lifecycle: 'REJECTED',
|
|
146
|
+
success: false,
|
|
147
|
+
error: 'Execution rejected: AuthorizationCapability is not allowed',
|
|
148
|
+
durationMs: Date.now() - startTime,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Cryptographic Signature check
|
|
152
|
+
const isSignatureValid = (0, core_1.verifyCapabilitySignature)(authorization, this.secretKey);
|
|
153
|
+
if (!isSignatureValid) {
|
|
154
|
+
return {
|
|
155
|
+
actionId,
|
|
156
|
+
executionId: authorization.executionId || 'invalid_sig',
|
|
157
|
+
toolName,
|
|
158
|
+
lifecycle: 'REJECTED',
|
|
159
|
+
success: false,
|
|
160
|
+
error: 'Execution rejected: Invalid or forged AuthorizationCapability signature',
|
|
161
|
+
durationMs: Date.now() - startTime,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Expiry check
|
|
165
|
+
if (authorization.expiresAt && new Date(authorization.expiresAt).getTime() <= Date.now()) {
|
|
166
|
+
return {
|
|
167
|
+
actionId,
|
|
168
|
+
executionId: authorization.executionId,
|
|
169
|
+
toolName,
|
|
170
|
+
lifecycle: 'REJECTED',
|
|
171
|
+
success: false,
|
|
172
|
+
error: 'Execution rejected: AuthorizationCapability has expired',
|
|
173
|
+
durationMs: Date.now() - startTime,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// Structural binding verification
|
|
177
|
+
if (authorization.actionId !== action.actionId) {
|
|
178
|
+
return {
|
|
179
|
+
actionId,
|
|
180
|
+
executionId: authorization.executionId,
|
|
181
|
+
toolName,
|
|
182
|
+
lifecycle: 'REJECTED',
|
|
183
|
+
success: false,
|
|
184
|
+
error: `Execution rejected: ActionId mismatch (authorized: "${authorization.actionId}", action: "${action.actionId}")`,
|
|
185
|
+
durationMs: Date.now() - startTime,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (authorization.toolName !== action.toolName) {
|
|
189
|
+
return {
|
|
190
|
+
actionId,
|
|
191
|
+
executionId: authorization.executionId,
|
|
192
|
+
toolName,
|
|
193
|
+
lifecycle: 'REJECTED',
|
|
194
|
+
success: false,
|
|
195
|
+
error: `Execution rejected: ToolName mismatch (authorized: "${authorization.toolName}", action: "${action.toolName}")`,
|
|
196
|
+
durationMs: Date.now() - startTime,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const currentParamsHash = (0, core_1.computeParametersHash)(action.parameters);
|
|
200
|
+
if (authorization.parametersHash !== currentParamsHash) {
|
|
201
|
+
return {
|
|
202
|
+
actionId,
|
|
203
|
+
executionId: authorization.executionId,
|
|
204
|
+
toolName,
|
|
205
|
+
lifecycle: 'REJECTED',
|
|
206
|
+
success: false,
|
|
207
|
+
error: 'Execution rejected: Parameters hash mismatch between intent and authorization',
|
|
208
|
+
durationMs: Date.now() - startTime,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const executionId = authorization.executionId;
|
|
212
|
+
// 2. Concurrency-Safe Persistent Idempotency Reservation
|
|
213
|
+
const existingExecution = await this.store.getExecution(executionId);
|
|
214
|
+
if (existingExecution) {
|
|
215
|
+
if (existingExecution.lifecycle === 'COMPLETED') {
|
|
216
|
+
return {
|
|
217
|
+
actionId,
|
|
218
|
+
executionId,
|
|
219
|
+
toolName,
|
|
220
|
+
lifecycle: 'COMPLETED',
|
|
221
|
+
success: true,
|
|
222
|
+
result: existingExecution.result,
|
|
223
|
+
durationMs: Date.now() - startTime,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
else if (existingExecution.lifecycle === 'EXECUTING') {
|
|
227
|
+
return {
|
|
228
|
+
actionId,
|
|
229
|
+
executionId,
|
|
230
|
+
toolName,
|
|
231
|
+
lifecycle: 'FAILED',
|
|
232
|
+
success: false,
|
|
233
|
+
error: 'Concurrent execution already in progress for this executionId',
|
|
234
|
+
durationMs: Date.now() - startTime,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const reservationSuccess = await this.store.reserveExecution({
|
|
239
|
+
executionId,
|
|
240
|
+
actionId,
|
|
241
|
+
toolName,
|
|
242
|
+
providerId: authorization.providerId,
|
|
243
|
+
parametersHash: currentParamsHash,
|
|
244
|
+
lifecycle: 'EXECUTING',
|
|
245
|
+
createdAt: new Date().toISOString(),
|
|
246
|
+
updatedAt: new Date().toISOString(),
|
|
247
|
+
});
|
|
248
|
+
if (!reservationSuccess) {
|
|
249
|
+
return {
|
|
250
|
+
actionId,
|
|
251
|
+
executionId,
|
|
252
|
+
toolName,
|
|
253
|
+
lifecycle: 'FAILED',
|
|
254
|
+
success: false,
|
|
255
|
+
error: 'Concurrent execution reservation conflict for executionId',
|
|
256
|
+
durationMs: Date.now() - startTime,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// Ensure initialization if needed before tool lookup
|
|
260
|
+
if (!this.initialized && this.mcpProviders.size > 0) {
|
|
261
|
+
await this.initialize();
|
|
262
|
+
}
|
|
263
|
+
// 3. Tool Lookup
|
|
264
|
+
const handler = this.findHandler(action.toolName);
|
|
265
|
+
if (!handler) {
|
|
266
|
+
const notFoundResult = {
|
|
267
|
+
actionId,
|
|
268
|
+
executionId,
|
|
269
|
+
toolName,
|
|
270
|
+
lifecycle: 'FAILED',
|
|
271
|
+
success: false,
|
|
272
|
+
error: `Tool "${action.toolName}" is not registered in Hands organ`,
|
|
273
|
+
durationMs: Date.now() - startTime,
|
|
274
|
+
};
|
|
275
|
+
await this.store.updateExecution({
|
|
276
|
+
executionId,
|
|
277
|
+
actionId,
|
|
278
|
+
toolName,
|
|
279
|
+
providerId: authorization.providerId,
|
|
280
|
+
parametersHash: currentParamsHash,
|
|
281
|
+
lifecycle: 'FAILED',
|
|
282
|
+
error: notFoundResult.error,
|
|
283
|
+
createdAt: new Date().toISOString(),
|
|
284
|
+
updatedAt: new Date().toISOString(),
|
|
285
|
+
});
|
|
286
|
+
return notFoundResult;
|
|
287
|
+
}
|
|
288
|
+
// 4. Recursive Input Schema Validation
|
|
289
|
+
const schemaValidation = (0, schema_validator_1.validateInputSchema)(handler.definition.inputSchema, action.parameters);
|
|
290
|
+
if (!schemaValidation.valid) {
|
|
291
|
+
const validationFailedResult = {
|
|
292
|
+
actionId,
|
|
293
|
+
executionId,
|
|
294
|
+
toolName,
|
|
295
|
+
lifecycle: 'REJECTED',
|
|
296
|
+
success: false,
|
|
297
|
+
error: `Parameter schema validation failed: ${schemaValidation.errors.join('; ')}`,
|
|
298
|
+
durationMs: Date.now() - startTime,
|
|
299
|
+
};
|
|
300
|
+
await this.store.updateExecution({
|
|
301
|
+
executionId,
|
|
302
|
+
actionId,
|
|
303
|
+
toolName,
|
|
304
|
+
providerId: authorization.providerId,
|
|
305
|
+
parametersHash: currentParamsHash,
|
|
306
|
+
lifecycle: 'REJECTED',
|
|
307
|
+
error: validationFailedResult.error,
|
|
308
|
+
createdAt: new Date().toISOString(),
|
|
309
|
+
updatedAt: new Date().toISOString(),
|
|
310
|
+
});
|
|
311
|
+
return validationFailedResult;
|
|
312
|
+
}
|
|
313
|
+
// 5. Execution with Timeout and Cancellation
|
|
314
|
+
const timeoutMs = options?.timeoutMs || handler.definition.timeoutMs || this.defaultTimeoutMs;
|
|
315
|
+
const controller = new AbortController();
|
|
316
|
+
if (options?.signal) {
|
|
317
|
+
if (options.signal.aborted) {
|
|
318
|
+
controller.abort(options.signal.reason);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
options.signal.addEventListener('abort', () => controller.abort(options.signal?.reason), { once: true });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const effectiveSignal = controller.signal;
|
|
325
|
+
let timeoutHandle;
|
|
326
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
327
|
+
timeoutHandle = setTimeout(() => {
|
|
328
|
+
controller.abort();
|
|
329
|
+
const err = new Error(`Tool execution timed out after ${timeoutMs}ms`);
|
|
330
|
+
err.isTimeout = true;
|
|
331
|
+
reject(err);
|
|
332
|
+
}, timeoutMs);
|
|
333
|
+
});
|
|
334
|
+
try {
|
|
335
|
+
const executionPromise = handler.execute(action.parameters, effectiveSignal);
|
|
336
|
+
const result = await Promise.race([executionPromise, timeoutPromise]);
|
|
337
|
+
clearTimeout(timeoutHandle);
|
|
338
|
+
const completedResult = {
|
|
339
|
+
actionId,
|
|
340
|
+
executionId,
|
|
341
|
+
toolName,
|
|
342
|
+
lifecycle: 'COMPLETED',
|
|
343
|
+
success: true,
|
|
344
|
+
result,
|
|
345
|
+
durationMs: Date.now() - startTime,
|
|
346
|
+
};
|
|
347
|
+
await this.store.updateExecution({
|
|
348
|
+
executionId,
|
|
349
|
+
actionId,
|
|
350
|
+
toolName,
|
|
351
|
+
providerId: authorization.providerId,
|
|
352
|
+
parametersHash: currentParamsHash,
|
|
353
|
+
lifecycle: 'COMPLETED',
|
|
354
|
+
result,
|
|
355
|
+
createdAt: new Date().toISOString(),
|
|
356
|
+
updatedAt: new Date().toISOString(),
|
|
357
|
+
});
|
|
358
|
+
return completedResult;
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
clearTimeout(timeoutHandle);
|
|
362
|
+
const isTimeout = err?.isTimeout || err?.name === 'AbortError' || err?.message?.includes('timed out');
|
|
363
|
+
const lifecycleState = isTimeout
|
|
364
|
+
? 'TIMED_OUT'
|
|
365
|
+
: effectiveSignal.aborted
|
|
366
|
+
? 'CANCELLED'
|
|
367
|
+
: 'FAILED';
|
|
368
|
+
const failedResult = {
|
|
369
|
+
actionId,
|
|
370
|
+
executionId,
|
|
371
|
+
toolName,
|
|
372
|
+
lifecycle: lifecycleState,
|
|
373
|
+
success: false,
|
|
374
|
+
error: err?.message || 'Tool execution failed',
|
|
375
|
+
durationMs: Date.now() - startTime,
|
|
376
|
+
};
|
|
377
|
+
await this.store.updateExecution({
|
|
378
|
+
executionId,
|
|
379
|
+
actionId,
|
|
380
|
+
toolName,
|
|
381
|
+
providerId: authorization.providerId,
|
|
382
|
+
parametersHash: currentParamsHash,
|
|
383
|
+
lifecycle: lifecycleState,
|
|
384
|
+
error: failedResult.error,
|
|
385
|
+
createdAt: new Date().toISOString(),
|
|
386
|
+
updatedAt: new Date().toISOString(),
|
|
387
|
+
});
|
|
388
|
+
return failedResult;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async close() {
|
|
392
|
+
for (const provider of this.mcpProviders.values()) {
|
|
393
|
+
await provider.disconnect();
|
|
394
|
+
}
|
|
395
|
+
this.mcpProviders.clear();
|
|
396
|
+
this.toolRegistry.clear();
|
|
397
|
+
this.providerTools.clear();
|
|
398
|
+
this.initialized = false;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
exports.DefaultHandsOrgan = DefaultHandsOrgan;
|
|
402
|
+
function probeHandsHealth(context) {
|
|
403
|
+
const secret = context?.config?.secretKey || (context?.env !== undefined ? context.env.ACTION_POLICY_SECRET : process.env.ACTION_POLICY_SECRET);
|
|
404
|
+
if (!secret && process.env.NODE_ENV === 'production') {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
message: 'Missing ACTION_POLICY_SECRET in production environment.',
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
return { ok: true, message: 'Hands organ configured and operational' };
|
|
411
|
+
}
|
|
412
|
+
__exportStar(require("./schema-validator"), exports);
|
|
413
|
+
__exportStar(require("./mcp-provider"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|