multicorn-shield 0.11.0 → 0.12.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/CHANGELOG.md +30 -0
- package/dist/badge.js +4 -4
- package/dist/index.cjs +25 -19
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +24 -19
- package/dist/multicorn-proxy.js +181 -9
- package/dist/openclaw-hook/handler.js +0 -1
- package/dist/openclaw-plugin/multicorn-shield.js +11 -17
- package/dist/openclaw-plugin/openclaw.plugin.json +3 -1
- package/dist/proxy.cjs +174 -0
- package/dist/proxy.d.cts +228 -1
- package/dist/proxy.d.ts +228 -1
- package/dist/proxy.js +174 -1
- package/dist/shield-extension.js +1 -4
- package/package.json +3 -2
- package/plugins/cline/README.md +61 -0
- package/plugins/cline/hooks/scripts/post-tool-use.cjs +116 -0
- package/plugins/cline/hooks/scripts/pre-tool-use.cjs +271 -0
- package/plugins/cline/hooks/scripts/shared.cjs +303 -0
package/dist/proxy.cjs
CHANGED
|
@@ -437,12 +437,186 @@ function mapMcpToolToScope(toolName) {
|
|
|
437
437
|
return { service: head, permissionLevel, actionType };
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
// src/logger/action-logger.ts
|
|
441
|
+
function createActionLogger(config) {
|
|
442
|
+
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
"[ActionLogger] API key is required. Provide it via the 'apiKey' config option."
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
const baseUrl = config.baseUrl ?? "https://api.multicorn.ai";
|
|
448
|
+
const timeout = config.timeout ?? 5e3;
|
|
449
|
+
if (!baseUrl.startsWith("https://") && !baseUrl.startsWith("http://localhost")) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`[ActionLogger] Base URL must use HTTPS for security. Received: "${baseUrl}". Use https:// or http://localhost for local development.`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const endpoint = `${baseUrl}/api/v1/actions`;
|
|
455
|
+
const batchEnabled = config.batchMode?.enabled ?? false;
|
|
456
|
+
const maxBatchSize = config.batchMode?.maxSize ?? 10;
|
|
457
|
+
const flushInterval = config.batchMode?.flushIntervalMs ?? 5e3;
|
|
458
|
+
const queue = [];
|
|
459
|
+
let flushTimer;
|
|
460
|
+
let isShutdown = false;
|
|
461
|
+
async function sendActions(actions) {
|
|
462
|
+
if (actions.length === 0) return;
|
|
463
|
+
const convertAction = (action) => ({
|
|
464
|
+
agent: action.agent,
|
|
465
|
+
service: action.service,
|
|
466
|
+
actionType: action.actionType,
|
|
467
|
+
status: action.status,
|
|
468
|
+
...action.cost !== void 0 ? { cost: action.cost } : {},
|
|
469
|
+
...action.metadata !== void 0 ? { metadata: action.metadata } : {}
|
|
470
|
+
});
|
|
471
|
+
const convertedActions = actions.map(convertAction);
|
|
472
|
+
const payload = batchEnabled ? { actions: convertedActions } : convertedActions[0];
|
|
473
|
+
let lastError;
|
|
474
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
475
|
+
try {
|
|
476
|
+
const controller = new AbortController();
|
|
477
|
+
const timeoutId = setTimeout(() => {
|
|
478
|
+
controller.abort();
|
|
479
|
+
}, timeout);
|
|
480
|
+
const response = await fetch(endpoint, {
|
|
481
|
+
method: "POST",
|
|
482
|
+
headers: {
|
|
483
|
+
"Content-Type": "application/json",
|
|
484
|
+
"X-Multicorn-Key": config.apiKey
|
|
485
|
+
},
|
|
486
|
+
body: JSON.stringify(payload),
|
|
487
|
+
signal: controller.signal
|
|
488
|
+
});
|
|
489
|
+
clearTimeout(timeoutId);
|
|
490
|
+
if (response.ok) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (response.status >= 400 && response.status < 500) {
|
|
494
|
+
const body = await response.text().catch(() => "");
|
|
495
|
+
throw new Error(
|
|
496
|
+
`[ActionLogger] Client error (${String(response.status)}): ${response.statusText}. Response: ${body}. Check your API key and payload format.`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (response.status >= 500 && attempt === 0) {
|
|
500
|
+
lastError = new Error(
|
|
501
|
+
`[ActionLogger] Server error (${String(response.status)}): ${response.statusText}. Retrying once...`
|
|
502
|
+
);
|
|
503
|
+
await sleep(100 * Math.pow(2, attempt));
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
throw new Error(
|
|
507
|
+
`[ActionLogger] Server error (${String(response.status)}) after retry: ${response.statusText}. Multicorn API may be experiencing issues.`
|
|
508
|
+
);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
if (error instanceof Error) {
|
|
511
|
+
if (error.name === "AbortError") {
|
|
512
|
+
lastError = new Error(
|
|
513
|
+
`[ActionLogger] Request timeout after ${String(timeout)}ms. Increase the 'timeout' config option or check your network connection.`
|
|
514
|
+
);
|
|
515
|
+
} else if (error.message.includes("Client error") || error.message.includes("Server error")) {
|
|
516
|
+
lastError = error;
|
|
517
|
+
} else {
|
|
518
|
+
lastError = new Error(
|
|
519
|
+
`[ActionLogger] Network error: ${error.message}. Check your network connection and API endpoint.`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
lastError = new Error(`[ActionLogger] Unknown error: ${String(error)}`);
|
|
524
|
+
}
|
|
525
|
+
if (attempt === 0 && !lastError.message.includes("Client error")) {
|
|
526
|
+
await sleep(100 * Math.pow(2, attempt));
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (lastError) {
|
|
533
|
+
if (config.onError) {
|
|
534
|
+
config.onError(lastError);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async function flushQueue() {
|
|
539
|
+
if (queue.length === 0) return;
|
|
540
|
+
const actions = queue.map((item) => item.payload);
|
|
541
|
+
queue.length = 0;
|
|
542
|
+
await sendActions(actions);
|
|
543
|
+
}
|
|
544
|
+
function startFlushTimer() {
|
|
545
|
+
if (flushTimer !== void 0) return;
|
|
546
|
+
flushTimer = setInterval(() => {
|
|
547
|
+
flushQueue().catch(() => {
|
|
548
|
+
});
|
|
549
|
+
}, flushInterval);
|
|
550
|
+
const timer = flushTimer;
|
|
551
|
+
if (typeof timer.unref === "function") {
|
|
552
|
+
timer.unref();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function stopFlushTimer() {
|
|
556
|
+
if (flushTimer) {
|
|
557
|
+
clearInterval(flushTimer);
|
|
558
|
+
flushTimer = void 0;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (batchEnabled) {
|
|
562
|
+
startFlushTimer();
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
logAction(action) {
|
|
566
|
+
if (isShutdown) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
"[ActionLogger] Cannot log action after shutdown. Create a new logger instance."
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
if (action.agent.trim().length === 0) {
|
|
572
|
+
throw new Error("[ActionLogger] Action must have a non-empty 'agent' field.");
|
|
573
|
+
}
|
|
574
|
+
if (action.service.trim().length === 0) {
|
|
575
|
+
throw new Error("[ActionLogger] Action must have a non-empty 'service' field.");
|
|
576
|
+
}
|
|
577
|
+
if (action.actionType.trim().length === 0) {
|
|
578
|
+
throw new Error("[ActionLogger] Action must have a non-empty 'actionType' field.");
|
|
579
|
+
}
|
|
580
|
+
if (action.status.trim().length === 0) {
|
|
581
|
+
throw new Error("[ActionLogger] Action must have a non-empty 'status' field.");
|
|
582
|
+
}
|
|
583
|
+
if (batchEnabled) {
|
|
584
|
+
queue.push({ payload: action, timestamp: Date.now() });
|
|
585
|
+
if (queue.length >= maxBatchSize) {
|
|
586
|
+
flushQueue().catch(() => {
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
sendActions([action]).catch(() => {
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
return Promise.resolve();
|
|
594
|
+
},
|
|
595
|
+
async flush() {
|
|
596
|
+
if (!batchEnabled) return;
|
|
597
|
+
await flushQueue();
|
|
598
|
+
},
|
|
599
|
+
async shutdown() {
|
|
600
|
+
if (isShutdown) return;
|
|
601
|
+
isShutdown = true;
|
|
602
|
+
stopFlushTimer();
|
|
603
|
+
if (batchEnabled) {
|
|
604
|
+
await flushQueue();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
function sleep(ms) {
|
|
610
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
611
|
+
}
|
|
612
|
+
|
|
440
613
|
exports.ShieldAuthError = ShieldAuthError;
|
|
441
614
|
exports.buildAuthErrorResponse = buildAuthErrorResponse;
|
|
442
615
|
exports.buildBlockedResponse = buildBlockedResponse;
|
|
443
616
|
exports.buildInternalErrorResponse = buildInternalErrorResponse;
|
|
444
617
|
exports.buildServiceUnreachableResponse = buildServiceUnreachableResponse;
|
|
445
618
|
exports.buildSpendingBlockedResponse = buildSpendingBlockedResponse;
|
|
619
|
+
exports.createActionLogger = createActionLogger;
|
|
446
620
|
exports.createLogger = createLogger;
|
|
447
621
|
exports.deriveDashboardUrl = deriveDashboardUrl;
|
|
448
622
|
exports.extractActionFromToolName = extractActionFromToolName;
|
package/dist/proxy.d.cts
CHANGED
|
@@ -68,6 +68,23 @@ declare const PERMISSION_LEVELS: {
|
|
|
68
68
|
readonly Create: "create";
|
|
69
69
|
};
|
|
70
70
|
type PermissionLevel = (typeof PERMISSION_LEVELS)[keyof typeof PERMISSION_LEVELS];
|
|
71
|
+
/**
|
|
72
|
+
* Lifecycle states for an action processed by the policy engine.
|
|
73
|
+
*
|
|
74
|
+
* - `approved`: action passed policy checks and was executed
|
|
75
|
+
* - `blocked`: action was denied by policy
|
|
76
|
+
* - `pending`: action is awaiting human approval
|
|
77
|
+
* - `flagged`: action was executed but flagged for review
|
|
78
|
+
* - `requires_approval`: action requires content review before execution
|
|
79
|
+
*/
|
|
80
|
+
declare const ACTION_STATUSES: {
|
|
81
|
+
readonly Approved: "approved";
|
|
82
|
+
readonly Blocked: "blocked";
|
|
83
|
+
readonly Pending: "pending";
|
|
84
|
+
readonly Flagged: "flagged";
|
|
85
|
+
readonly RequiresApproval: "requires_approval";
|
|
86
|
+
};
|
|
87
|
+
type ActionStatus = (typeof ACTION_STATUSES)[keyof typeof ACTION_STATUSES];
|
|
71
88
|
/**
|
|
72
89
|
* A single permission scope binding a service to an access level.
|
|
73
90
|
*
|
|
@@ -232,4 +249,214 @@ interface McpToolScopeMapping {
|
|
|
232
249
|
*/
|
|
233
250
|
declare function mapMcpToolToScope(toolName: string): McpToolScopeMapping;
|
|
234
251
|
|
|
235
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Action logging client for Multicorn Shield.
|
|
254
|
+
*
|
|
255
|
+
* Sends structured action events to the Multicorn hosted API, providing the
|
|
256
|
+
* observability backbone for Shield. Every agent action is logged with
|
|
257
|
+
* metadata, cost information, and status tracking.
|
|
258
|
+
*
|
|
259
|
+
* **Design principles:**
|
|
260
|
+
* - **Fire-and-forget**: Logging failures MUST NOT block the agent's action.
|
|
261
|
+
* - **Security (Jordan persona)**: API keys never logged, HTTPS enforced.
|
|
262
|
+
* - **Clear errors (Yuki persona)**: Descriptive messages for all failure modes.
|
|
263
|
+
* - **Clean async (The Team persona)**: Proper promise handling, no hanging requests.
|
|
264
|
+
*
|
|
265
|
+
* @module logger/action-logger
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Configuration options for the action logger client.
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```ts
|
|
273
|
+
* const config: ActionLoggerConfig = {
|
|
274
|
+
* apiKey: process.env.MULTICORN_API_KEY,
|
|
275
|
+
* baseUrl: "https://api.multicorn.ai",
|
|
276
|
+
* timeout: 5000,
|
|
277
|
+
* batchMode: {
|
|
278
|
+
* enabled: true,
|
|
279
|
+
* maxSize: 10,
|
|
280
|
+
* flushIntervalMs: 5000,
|
|
281
|
+
* },
|
|
282
|
+
* };
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
interface ActionLoggerConfig {
|
|
286
|
+
/**
|
|
287
|
+
* Multicorn API key for authentication.
|
|
288
|
+
* Passed as `X-Multicorn-Key` header. Never logged.
|
|
289
|
+
*/
|
|
290
|
+
readonly apiKey: string;
|
|
291
|
+
/**
|
|
292
|
+
* Base URL for the Multicorn API.
|
|
293
|
+
* @default "https://api.multicorn.ai"
|
|
294
|
+
*/
|
|
295
|
+
readonly baseUrl?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Request timeout in milliseconds.
|
|
298
|
+
* @default 5000
|
|
299
|
+
*/
|
|
300
|
+
readonly timeout?: number;
|
|
301
|
+
/**
|
|
302
|
+
* Optional batch mode configuration.
|
|
303
|
+
* When enabled, actions are queued and flushed periodically.
|
|
304
|
+
*/
|
|
305
|
+
readonly batchMode?: BatchModeConfig;
|
|
306
|
+
/**
|
|
307
|
+
* Optional error handler for logging failures.
|
|
308
|
+
* Called asynchronously. Does not block the main action flow.
|
|
309
|
+
*/
|
|
310
|
+
readonly onError?: (error: Error) => void;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Batch mode configuration.
|
|
314
|
+
*
|
|
315
|
+
* Actions are flushed when **either** condition is met:
|
|
316
|
+
* - The queue reaches `maxSize` actions, OR
|
|
317
|
+
* - `flushIntervalMs` milliseconds have elapsed since the last flush.
|
|
318
|
+
*/
|
|
319
|
+
interface BatchModeConfig {
|
|
320
|
+
/** Whether batch mode is enabled. */
|
|
321
|
+
readonly enabled: boolean;
|
|
322
|
+
/**
|
|
323
|
+
* Maximum number of actions to queue before forcing a flush.
|
|
324
|
+
* @default 10
|
|
325
|
+
*/
|
|
326
|
+
readonly maxSize?: number;
|
|
327
|
+
/**
|
|
328
|
+
* Maximum time (ms) between flushes.
|
|
329
|
+
* @default 5000
|
|
330
|
+
*/
|
|
331
|
+
readonly flushIntervalMs?: number;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* A single action event to be logged.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* const action: ActionPayload = {
|
|
339
|
+
* agent: "inbox-assistant",
|
|
340
|
+
* service: "gmail",
|
|
341
|
+
* actionType: "send_email",
|
|
342
|
+
* status: "approved",
|
|
343
|
+
* cost: 0.002,
|
|
344
|
+
* metadata: {
|
|
345
|
+
* recipient: "user@example.com",
|
|
346
|
+
* subject: "Weekly report",
|
|
347
|
+
* },
|
|
348
|
+
* };
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
interface ActionPayload {
|
|
352
|
+
/** Agent identifier (e.g. "inbox-assistant"). */
|
|
353
|
+
readonly agent: string;
|
|
354
|
+
/** Service being accessed (e.g. "gmail", "slack"). */
|
|
355
|
+
readonly service: string;
|
|
356
|
+
/** Type of action performed (e.g. "send_email", "read_message"). */
|
|
357
|
+
readonly actionType: string;
|
|
358
|
+
/** Lifecycle status of the action. */
|
|
359
|
+
readonly status: ActionStatus;
|
|
360
|
+
/**
|
|
361
|
+
* Optional cost in USD incurred by this action.
|
|
362
|
+
* Present only for actions with usage-based pricing.
|
|
363
|
+
*/
|
|
364
|
+
readonly cost?: number;
|
|
365
|
+
/**
|
|
366
|
+
* Optional structured metadata for additional context.
|
|
367
|
+
* Keys and values must be serializable to JSON.
|
|
368
|
+
*/
|
|
369
|
+
readonly metadata?: Readonly<Record<string, string | number | boolean>>;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* HTTP client for logging agent actions to the Multicorn Shield API.
|
|
373
|
+
*
|
|
374
|
+
* Supports both immediate and batched delivery modes. All network failures
|
|
375
|
+
* are handled gracefully to ensure logging never blocks the agent's execution.
|
|
376
|
+
*
|
|
377
|
+
* @example Basic usage (immediate mode)
|
|
378
|
+
* ```ts
|
|
379
|
+
* const logger = createActionLogger({
|
|
380
|
+
* apiKey: process.env.MULTICORN_API_KEY!,
|
|
381
|
+
* });
|
|
382
|
+
*
|
|
383
|
+
* await logger.logAction({
|
|
384
|
+
* agent: "email-assistant",
|
|
385
|
+
* service: "gmail",
|
|
386
|
+
* actionType: "send_email",
|
|
387
|
+
* status: "approved",
|
|
388
|
+
* cost: 0.002,
|
|
389
|
+
* });
|
|
390
|
+
* ```
|
|
391
|
+
*
|
|
392
|
+
* @example Batch mode with error handling
|
|
393
|
+
* ```ts
|
|
394
|
+
* const logger = createActionLogger({
|
|
395
|
+
* apiKey: process.env.MULTICORN_API_KEY!,
|
|
396
|
+
* batchMode: { enabled: true, maxSize: 10, flushIntervalMs: 5000 },
|
|
397
|
+
* onError: (err) => console.error("[ActionLogger]", err.message),
|
|
398
|
+
* });
|
|
399
|
+
*
|
|
400
|
+
* // Actions are queued and flushed automatically
|
|
401
|
+
* logger.logAction({
|
|
402
|
+
* agent: "inbox-assistant",
|
|
403
|
+
* service: "gmail",
|
|
404
|
+
* actionType: "read_message",
|
|
405
|
+
* status: "approved",
|
|
406
|
+
* });
|
|
407
|
+
*
|
|
408
|
+
* // Force immediate flush
|
|
409
|
+
* await logger.flush();
|
|
410
|
+
*
|
|
411
|
+
* // Clean up resources
|
|
412
|
+
* logger.shutdown();
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
interface ActionLogger {
|
|
416
|
+
/**
|
|
417
|
+
* Log a single action event.
|
|
418
|
+
*
|
|
419
|
+
* In immediate mode, sends the action to the API right away (non-blocking).
|
|
420
|
+
* In batch mode, queues the action and flushes when thresholds are met.
|
|
421
|
+
*
|
|
422
|
+
* @param action - The action event to log.
|
|
423
|
+
* @returns A promise that resolves when the action is sent (immediate mode)
|
|
424
|
+
* or queued (batch mode). Rejects only on validation errors, not
|
|
425
|
+
* network failures (those are passed to `onError`).
|
|
426
|
+
*/
|
|
427
|
+
logAction(action: ActionPayload): Promise<void>;
|
|
428
|
+
/**
|
|
429
|
+
* Flush all queued actions immediately (batch mode only).
|
|
430
|
+
*
|
|
431
|
+
* In immediate mode, this is a no-op.
|
|
432
|
+
*
|
|
433
|
+
* @returns A promise that resolves when all queued actions have been sent.
|
|
434
|
+
*/
|
|
435
|
+
flush(): Promise<void>;
|
|
436
|
+
/**
|
|
437
|
+
* Shut down the logger and clean up resources.
|
|
438
|
+
*
|
|
439
|
+
* Stops the flush timer (if in batch mode) and flushes any remaining actions.
|
|
440
|
+
* After calling `shutdown()`, the logger should not be used.
|
|
441
|
+
*/
|
|
442
|
+
shutdown(): Promise<void>;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Create a new action logger client.
|
|
446
|
+
*
|
|
447
|
+
* @param config - Configuration options.
|
|
448
|
+
* @returns An {@link ActionLogger} instance.
|
|
449
|
+
* @throws {Error} If the API key is missing or the base URL is not HTTPS.
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```ts
|
|
453
|
+
* const logger = createActionLogger({
|
|
454
|
+
* apiKey: process.env.MULTICORN_API_KEY!,
|
|
455
|
+
* baseUrl: "https://api.multicorn.ai",
|
|
456
|
+
* timeout: 3000,
|
|
457
|
+
* });
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
declare function createActionLogger(config: ActionLoggerConfig): ActionLogger;
|
|
461
|
+
|
|
462
|
+
export { type ActionLogger, type ActionLoggerConfig, type ActionPayload, type AgentRecord, type JsonRpcError, type JsonRpcRequest, type JsonRpcResponse, type LogLevel, type PermissionLevel, type ProxyLogger, type Scope, ShieldAuthError, type ToolCallParams, buildAuthErrorResponse, buildBlockedResponse, buildInternalErrorResponse, buildServiceUnreachableResponse, buildSpendingBlockedResponse, createActionLogger, createLogger, deriveDashboardUrl, extractActionFromToolName, extractServiceFromToolName, extractToolCallParams, fetchGrantedScopes, findAgentByName, isValidLogLevel, mapMcpToolToScope, parseJsonRpcLine, registerAgent, validateScopeAccess };
|
package/dist/proxy.d.ts
CHANGED
|
@@ -68,6 +68,23 @@ declare const PERMISSION_LEVELS: {
|
|
|
68
68
|
readonly Create: "create";
|
|
69
69
|
};
|
|
70
70
|
type PermissionLevel = (typeof PERMISSION_LEVELS)[keyof typeof PERMISSION_LEVELS];
|
|
71
|
+
/**
|
|
72
|
+
* Lifecycle states for an action processed by the policy engine.
|
|
73
|
+
*
|
|
74
|
+
* - `approved`: action passed policy checks and was executed
|
|
75
|
+
* - `blocked`: action was denied by policy
|
|
76
|
+
* - `pending`: action is awaiting human approval
|
|
77
|
+
* - `flagged`: action was executed but flagged for review
|
|
78
|
+
* - `requires_approval`: action requires content review before execution
|
|
79
|
+
*/
|
|
80
|
+
declare const ACTION_STATUSES: {
|
|
81
|
+
readonly Approved: "approved";
|
|
82
|
+
readonly Blocked: "blocked";
|
|
83
|
+
readonly Pending: "pending";
|
|
84
|
+
readonly Flagged: "flagged";
|
|
85
|
+
readonly RequiresApproval: "requires_approval";
|
|
86
|
+
};
|
|
87
|
+
type ActionStatus = (typeof ACTION_STATUSES)[keyof typeof ACTION_STATUSES];
|
|
71
88
|
/**
|
|
72
89
|
* A single permission scope binding a service to an access level.
|
|
73
90
|
*
|
|
@@ -232,4 +249,214 @@ interface McpToolScopeMapping {
|
|
|
232
249
|
*/
|
|
233
250
|
declare function mapMcpToolToScope(toolName: string): McpToolScopeMapping;
|
|
234
251
|
|
|
235
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Action logging client for Multicorn Shield.
|
|
254
|
+
*
|
|
255
|
+
* Sends structured action events to the Multicorn hosted API, providing the
|
|
256
|
+
* observability backbone for Shield. Every agent action is logged with
|
|
257
|
+
* metadata, cost information, and status tracking.
|
|
258
|
+
*
|
|
259
|
+
* **Design principles:**
|
|
260
|
+
* - **Fire-and-forget**: Logging failures MUST NOT block the agent's action.
|
|
261
|
+
* - **Security (Jordan persona)**: API keys never logged, HTTPS enforced.
|
|
262
|
+
* - **Clear errors (Yuki persona)**: Descriptive messages for all failure modes.
|
|
263
|
+
* - **Clean async (The Team persona)**: Proper promise handling, no hanging requests.
|
|
264
|
+
*
|
|
265
|
+
* @module logger/action-logger
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Configuration options for the action logger client.
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```ts
|
|
273
|
+
* const config: ActionLoggerConfig = {
|
|
274
|
+
* apiKey: process.env.MULTICORN_API_KEY,
|
|
275
|
+
* baseUrl: "https://api.multicorn.ai",
|
|
276
|
+
* timeout: 5000,
|
|
277
|
+
* batchMode: {
|
|
278
|
+
* enabled: true,
|
|
279
|
+
* maxSize: 10,
|
|
280
|
+
* flushIntervalMs: 5000,
|
|
281
|
+
* },
|
|
282
|
+
* };
|
|
283
|
+
* ```
|
|
284
|
+
*/
|
|
285
|
+
interface ActionLoggerConfig {
|
|
286
|
+
/**
|
|
287
|
+
* Multicorn API key for authentication.
|
|
288
|
+
* Passed as `X-Multicorn-Key` header. Never logged.
|
|
289
|
+
*/
|
|
290
|
+
readonly apiKey: string;
|
|
291
|
+
/**
|
|
292
|
+
* Base URL for the Multicorn API.
|
|
293
|
+
* @default "https://api.multicorn.ai"
|
|
294
|
+
*/
|
|
295
|
+
readonly baseUrl?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Request timeout in milliseconds.
|
|
298
|
+
* @default 5000
|
|
299
|
+
*/
|
|
300
|
+
readonly timeout?: number;
|
|
301
|
+
/**
|
|
302
|
+
* Optional batch mode configuration.
|
|
303
|
+
* When enabled, actions are queued and flushed periodically.
|
|
304
|
+
*/
|
|
305
|
+
readonly batchMode?: BatchModeConfig;
|
|
306
|
+
/**
|
|
307
|
+
* Optional error handler for logging failures.
|
|
308
|
+
* Called asynchronously. Does not block the main action flow.
|
|
309
|
+
*/
|
|
310
|
+
readonly onError?: (error: Error) => void;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Batch mode configuration.
|
|
314
|
+
*
|
|
315
|
+
* Actions are flushed when **either** condition is met:
|
|
316
|
+
* - The queue reaches `maxSize` actions, OR
|
|
317
|
+
* - `flushIntervalMs` milliseconds have elapsed since the last flush.
|
|
318
|
+
*/
|
|
319
|
+
interface BatchModeConfig {
|
|
320
|
+
/** Whether batch mode is enabled. */
|
|
321
|
+
readonly enabled: boolean;
|
|
322
|
+
/**
|
|
323
|
+
* Maximum number of actions to queue before forcing a flush.
|
|
324
|
+
* @default 10
|
|
325
|
+
*/
|
|
326
|
+
readonly maxSize?: number;
|
|
327
|
+
/**
|
|
328
|
+
* Maximum time (ms) between flushes.
|
|
329
|
+
* @default 5000
|
|
330
|
+
*/
|
|
331
|
+
readonly flushIntervalMs?: number;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* A single action event to be logged.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* const action: ActionPayload = {
|
|
339
|
+
* agent: "inbox-assistant",
|
|
340
|
+
* service: "gmail",
|
|
341
|
+
* actionType: "send_email",
|
|
342
|
+
* status: "approved",
|
|
343
|
+
* cost: 0.002,
|
|
344
|
+
* metadata: {
|
|
345
|
+
* recipient: "user@example.com",
|
|
346
|
+
* subject: "Weekly report",
|
|
347
|
+
* },
|
|
348
|
+
* };
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
interface ActionPayload {
|
|
352
|
+
/** Agent identifier (e.g. "inbox-assistant"). */
|
|
353
|
+
readonly agent: string;
|
|
354
|
+
/** Service being accessed (e.g. "gmail", "slack"). */
|
|
355
|
+
readonly service: string;
|
|
356
|
+
/** Type of action performed (e.g. "send_email", "read_message"). */
|
|
357
|
+
readonly actionType: string;
|
|
358
|
+
/** Lifecycle status of the action. */
|
|
359
|
+
readonly status: ActionStatus;
|
|
360
|
+
/**
|
|
361
|
+
* Optional cost in USD incurred by this action.
|
|
362
|
+
* Present only for actions with usage-based pricing.
|
|
363
|
+
*/
|
|
364
|
+
readonly cost?: number;
|
|
365
|
+
/**
|
|
366
|
+
* Optional structured metadata for additional context.
|
|
367
|
+
* Keys and values must be serializable to JSON.
|
|
368
|
+
*/
|
|
369
|
+
readonly metadata?: Readonly<Record<string, string | number | boolean>>;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* HTTP client for logging agent actions to the Multicorn Shield API.
|
|
373
|
+
*
|
|
374
|
+
* Supports both immediate and batched delivery modes. All network failures
|
|
375
|
+
* are handled gracefully to ensure logging never blocks the agent's execution.
|
|
376
|
+
*
|
|
377
|
+
* @example Basic usage (immediate mode)
|
|
378
|
+
* ```ts
|
|
379
|
+
* const logger = createActionLogger({
|
|
380
|
+
* apiKey: process.env.MULTICORN_API_KEY!,
|
|
381
|
+
* });
|
|
382
|
+
*
|
|
383
|
+
* await logger.logAction({
|
|
384
|
+
* agent: "email-assistant",
|
|
385
|
+
* service: "gmail",
|
|
386
|
+
* actionType: "send_email",
|
|
387
|
+
* status: "approved",
|
|
388
|
+
* cost: 0.002,
|
|
389
|
+
* });
|
|
390
|
+
* ```
|
|
391
|
+
*
|
|
392
|
+
* @example Batch mode with error handling
|
|
393
|
+
* ```ts
|
|
394
|
+
* const logger = createActionLogger({
|
|
395
|
+
* apiKey: process.env.MULTICORN_API_KEY!,
|
|
396
|
+
* batchMode: { enabled: true, maxSize: 10, flushIntervalMs: 5000 },
|
|
397
|
+
* onError: (err) => console.error("[ActionLogger]", err.message),
|
|
398
|
+
* });
|
|
399
|
+
*
|
|
400
|
+
* // Actions are queued and flushed automatically
|
|
401
|
+
* logger.logAction({
|
|
402
|
+
* agent: "inbox-assistant",
|
|
403
|
+
* service: "gmail",
|
|
404
|
+
* actionType: "read_message",
|
|
405
|
+
* status: "approved",
|
|
406
|
+
* });
|
|
407
|
+
*
|
|
408
|
+
* // Force immediate flush
|
|
409
|
+
* await logger.flush();
|
|
410
|
+
*
|
|
411
|
+
* // Clean up resources
|
|
412
|
+
* logger.shutdown();
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
interface ActionLogger {
|
|
416
|
+
/**
|
|
417
|
+
* Log a single action event.
|
|
418
|
+
*
|
|
419
|
+
* In immediate mode, sends the action to the API right away (non-blocking).
|
|
420
|
+
* In batch mode, queues the action and flushes when thresholds are met.
|
|
421
|
+
*
|
|
422
|
+
* @param action - The action event to log.
|
|
423
|
+
* @returns A promise that resolves when the action is sent (immediate mode)
|
|
424
|
+
* or queued (batch mode). Rejects only on validation errors, not
|
|
425
|
+
* network failures (those are passed to `onError`).
|
|
426
|
+
*/
|
|
427
|
+
logAction(action: ActionPayload): Promise<void>;
|
|
428
|
+
/**
|
|
429
|
+
* Flush all queued actions immediately (batch mode only).
|
|
430
|
+
*
|
|
431
|
+
* In immediate mode, this is a no-op.
|
|
432
|
+
*
|
|
433
|
+
* @returns A promise that resolves when all queued actions have been sent.
|
|
434
|
+
*/
|
|
435
|
+
flush(): Promise<void>;
|
|
436
|
+
/**
|
|
437
|
+
* Shut down the logger and clean up resources.
|
|
438
|
+
*
|
|
439
|
+
* Stops the flush timer (if in batch mode) and flushes any remaining actions.
|
|
440
|
+
* After calling `shutdown()`, the logger should not be used.
|
|
441
|
+
*/
|
|
442
|
+
shutdown(): Promise<void>;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Create a new action logger client.
|
|
446
|
+
*
|
|
447
|
+
* @param config - Configuration options.
|
|
448
|
+
* @returns An {@link ActionLogger} instance.
|
|
449
|
+
* @throws {Error} If the API key is missing or the base URL is not HTTPS.
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```ts
|
|
453
|
+
* const logger = createActionLogger({
|
|
454
|
+
* apiKey: process.env.MULTICORN_API_KEY!,
|
|
455
|
+
* baseUrl: "https://api.multicorn.ai",
|
|
456
|
+
* timeout: 3000,
|
|
457
|
+
* });
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
declare function createActionLogger(config: ActionLoggerConfig): ActionLogger;
|
|
461
|
+
|
|
462
|
+
export { type ActionLogger, type ActionLoggerConfig, type ActionPayload, type AgentRecord, type JsonRpcError, type JsonRpcRequest, type JsonRpcResponse, type LogLevel, type PermissionLevel, type ProxyLogger, type Scope, ShieldAuthError, type ToolCallParams, buildAuthErrorResponse, buildBlockedResponse, buildInternalErrorResponse, buildServiceUnreachableResponse, buildSpendingBlockedResponse, createActionLogger, createLogger, deriveDashboardUrl, extractActionFromToolName, extractServiceFromToolName, extractToolCallParams, fetchGrantedScopes, findAgentByName, isValidLogLevel, mapMcpToolToScope, parseJsonRpcLine, registerAgent, validateScopeAccess };
|