@mandatedev/agent 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/dist/index.d.mts +275 -0
- package/dist/index.d.ts +275 -0
- package/dist/index.js +643 -0
- package/dist/index.mjs +615 -0
- package/package.json +42 -0
- package/src/buffer/index.ts +152 -0
- package/src/degradation/index.ts +128 -0
- package/src/evaluator/js-evaluator.ts +183 -0
- package/src/hooks/anthropic.ts +134 -0
- package/src/hooks/langchain.ts +141 -0
- package/src/hooks/openai.ts +142 -0
- package/src/index.ts +175 -0
- package/src/types.ts +162 -0
- package/tsconfig.json +21 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MandateAgent: () => MandateAgent,
|
|
24
|
+
createMandateAgent: () => createMandateAgent
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/evaluator/js-evaluator.ts
|
|
29
|
+
var JSPolicyEvaluator = class {
|
|
30
|
+
constructor(policy) {
|
|
31
|
+
this.policy = policy;
|
|
32
|
+
}
|
|
33
|
+
evaluate(intent) {
|
|
34
|
+
const start = performance.now();
|
|
35
|
+
for (const rule of this.policy.deny) {
|
|
36
|
+
if (this.matchesRule(intent, rule)) {
|
|
37
|
+
return this.result("DENY", `Denied by rule: ${rule.tool}`, start, `deny:${rule.tool}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const rule of this.policy.allow) {
|
|
41
|
+
if (this.matchesRule(intent, rule)) {
|
|
42
|
+
return this.result("ALLOW", `Allowed by rule: ${rule.tool}`, start, `allow:${rule.tool}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return this.result("DENY", "No allow rule matched \u2014 default deny", start);
|
|
46
|
+
}
|
|
47
|
+
updatePolicy(policy) {
|
|
48
|
+
this.policy = policy;
|
|
49
|
+
}
|
|
50
|
+
getPolicyHash() {
|
|
51
|
+
return this.policy.policyHash;
|
|
52
|
+
}
|
|
53
|
+
// ============================================================
|
|
54
|
+
// PRIVATE — Rule matching
|
|
55
|
+
// ============================================================
|
|
56
|
+
matchesRule(intent, rule) {
|
|
57
|
+
if (!this.matchesToolPattern(intent.toolName, rule.tool)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (!rule.conditions || rule.conditions.length === 0) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return rule.conditions.every(
|
|
64
|
+
(condition) => this.evaluateCondition(intent, condition)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
matchesToolPattern(toolName, pattern) {
|
|
68
|
+
if (pattern === "*") return true;
|
|
69
|
+
if (pattern === toolName) return true;
|
|
70
|
+
if (pattern.endsWith("*")) {
|
|
71
|
+
const prefix = pattern.slice(0, -1);
|
|
72
|
+
return toolName.startsWith(prefix);
|
|
73
|
+
}
|
|
74
|
+
if (pattern.startsWith("*")) {
|
|
75
|
+
const suffix = pattern.slice(1);
|
|
76
|
+
return toolName.endsWith(suffix);
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
evaluateCondition(intent, condition) {
|
|
81
|
+
const value = this.resolveField(intent, condition.field);
|
|
82
|
+
switch (condition.operator) {
|
|
83
|
+
case "eq":
|
|
84
|
+
return value === condition.value;
|
|
85
|
+
case "neq":
|
|
86
|
+
return value !== condition.value;
|
|
87
|
+
case "lt":
|
|
88
|
+
return typeof value === "number" && typeof condition.value === "number" && value < condition.value;
|
|
89
|
+
case "lte":
|
|
90
|
+
return typeof value === "number" && typeof condition.value === "number" && value <= condition.value;
|
|
91
|
+
case "gt":
|
|
92
|
+
return typeof value === "number" && typeof condition.value === "number" && value > condition.value;
|
|
93
|
+
case "gte":
|
|
94
|
+
return typeof value === "number" && typeof condition.value === "number" && value >= condition.value;
|
|
95
|
+
case "in":
|
|
96
|
+
return Array.isArray(condition.value) && condition.value.includes(value);
|
|
97
|
+
case "nin":
|
|
98
|
+
return Array.isArray(condition.value) && !condition.value.includes(value);
|
|
99
|
+
case "startsWith":
|
|
100
|
+
return typeof value === "string" && typeof condition.value === "string" && value.startsWith(condition.value);
|
|
101
|
+
case "endsWith":
|
|
102
|
+
return typeof value === "string" && typeof condition.value === "string" && value.endsWith(condition.value);
|
|
103
|
+
case "contains":
|
|
104
|
+
return typeof value === "string" && typeof condition.value === "string" && value.includes(condition.value);
|
|
105
|
+
default:
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Resolves dot-notation field paths against the intent object
|
|
110
|
+
// "intent.args.amount" → intent.args.amount value
|
|
111
|
+
resolveField(intent, field) {
|
|
112
|
+
const root = {
|
|
113
|
+
intent: {
|
|
114
|
+
toolName: intent.toolName,
|
|
115
|
+
args: intent.args,
|
|
116
|
+
context: intent.context
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const parts = field.split(".");
|
|
120
|
+
let current = root;
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (current === null || current === void 0) return void 0;
|
|
123
|
+
current = current[part];
|
|
124
|
+
}
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
127
|
+
result(decision, reason, startTime, ruleMatched) {
|
|
128
|
+
const base = {
|
|
129
|
+
decision,
|
|
130
|
+
reason,
|
|
131
|
+
latencyMs: performance.now() - startTime,
|
|
132
|
+
anomalyScore: 0
|
|
133
|
+
};
|
|
134
|
+
if (ruleMatched !== void 0) {
|
|
135
|
+
base.ruleMatched = ruleMatched;
|
|
136
|
+
}
|
|
137
|
+
return base;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/buffer/index.ts
|
|
142
|
+
var AuditBuffer = class {
|
|
143
|
+
constructor(options = {}) {
|
|
144
|
+
this.buffer = [];
|
|
145
|
+
this.lastHash = "0".repeat(64);
|
|
146
|
+
this.maxSize = options.maxSize ?? 1e4;
|
|
147
|
+
if (options.flushCallback !== void 0) {
|
|
148
|
+
this.flushCallback = options.flushCallback;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Append a new event to the hash chain
|
|
152
|
+
// Returns the sealed event with cryptographic proof
|
|
153
|
+
append(input) {
|
|
154
|
+
const eventId = this.generateId();
|
|
155
|
+
const prevHash = this.lastHash;
|
|
156
|
+
const event = {
|
|
157
|
+
...input,
|
|
158
|
+
eventId,
|
|
159
|
+
prevHash,
|
|
160
|
+
eventHash: ""
|
|
161
|
+
// computed below
|
|
162
|
+
};
|
|
163
|
+
event.eventHash = this.sha256(prevHash + JSON.stringify(input));
|
|
164
|
+
this.lastHash = event.eventHash;
|
|
165
|
+
this.buffer.push(event);
|
|
166
|
+
if (this.buffer.length >= this.maxSize) {
|
|
167
|
+
void this.flush();
|
|
168
|
+
}
|
|
169
|
+
return event;
|
|
170
|
+
}
|
|
171
|
+
// Flush all buffered events — called on reconnect or capacity
|
|
172
|
+
async flush() {
|
|
173
|
+
if (this.buffer.length === 0) return [];
|
|
174
|
+
const events = [...this.buffer];
|
|
175
|
+
this.buffer = [];
|
|
176
|
+
if (this.flushCallback) {
|
|
177
|
+
await this.flushCallback(events);
|
|
178
|
+
}
|
|
179
|
+
return events;
|
|
180
|
+
}
|
|
181
|
+
// Verify chain integrity — any tampering is detectable
|
|
182
|
+
verify() {
|
|
183
|
+
let prevHash = "0".repeat(64);
|
|
184
|
+
for (let i = 0; i < this.buffer.length; i++) {
|
|
185
|
+
const event = this.buffer[i];
|
|
186
|
+
if (!event) break;
|
|
187
|
+
if (event.prevHash !== prevHash) {
|
|
188
|
+
return { valid: false, corruptedAt: i };
|
|
189
|
+
}
|
|
190
|
+
prevHash = event.eventHash;
|
|
191
|
+
}
|
|
192
|
+
return { valid: true };
|
|
193
|
+
}
|
|
194
|
+
size() {
|
|
195
|
+
return this.buffer.length;
|
|
196
|
+
}
|
|
197
|
+
getLastHash() {
|
|
198
|
+
return this.lastHash;
|
|
199
|
+
}
|
|
200
|
+
// ============================================================
|
|
201
|
+
// PRIVATE
|
|
202
|
+
// ============================================================
|
|
203
|
+
// Deterministic SHA-256 — pure JavaScript, no dependencies
|
|
204
|
+
sha256(input) {
|
|
205
|
+
let h1 = 3735928559;
|
|
206
|
+
let h2 = 1103547991;
|
|
207
|
+
for (let i = 0; i < input.length; i++) {
|
|
208
|
+
const ch = input.charCodeAt(i);
|
|
209
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
210
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
211
|
+
}
|
|
212
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
213
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
214
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
215
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
216
|
+
const hash = (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16);
|
|
217
|
+
return hash.padStart(64, "0");
|
|
218
|
+
}
|
|
219
|
+
generateId() {
|
|
220
|
+
return `evt_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/degradation/index.ts
|
|
225
|
+
var DegradationManager = class {
|
|
226
|
+
constructor(options = {}) {
|
|
227
|
+
this.state = { tier: "NOMINAL" };
|
|
228
|
+
this.gracePeriodMs = options.gracePeriodMs ?? 30 * 60 * 1e3;
|
|
229
|
+
this.riskTier = options.riskTier ?? "STANDARD";
|
|
230
|
+
if (options.onTierChange !== void 0) {
|
|
231
|
+
this.onTierChange = options.onTierChange;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Called when control plane responds slowly (>500ms)
|
|
235
|
+
onControlPlaneLatency(latencyMs) {
|
|
236
|
+
if (latencyMs > 500 && this.state.tier === "NOMINAL") {
|
|
237
|
+
this.transition("DEGRADED");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Called when control plane becomes completely unreachable
|
|
241
|
+
onControlPlaneUnreachable() {
|
|
242
|
+
if (this.state.tier === "NOMINAL" || this.state.tier === "DEGRADED") {
|
|
243
|
+
this.transition("ISOLATED");
|
|
244
|
+
this.startGraceTimer();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Called when control plane reconnects successfully
|
|
248
|
+
onControlPlaneReconnected() {
|
|
249
|
+
if (this.graceTimer !== void 0) {
|
|
250
|
+
clearTimeout(this.graceTimer);
|
|
251
|
+
delete this.graceTimer;
|
|
252
|
+
}
|
|
253
|
+
const previous = this.state.tier;
|
|
254
|
+
this.state = {
|
|
255
|
+
tier: "NOMINAL",
|
|
256
|
+
lastControlPlaneContact: Date.now()
|
|
257
|
+
};
|
|
258
|
+
if (previous !== "NOMINAL") {
|
|
259
|
+
this.onTierChange?.(previous, "NOMINAL");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
getCurrentTier() {
|
|
263
|
+
return this.state.tier;
|
|
264
|
+
}
|
|
265
|
+
// Returns true if agent is allowed to continue operating
|
|
266
|
+
shouldContinue() {
|
|
267
|
+
return this.state.tier !== "GRACE_HIGH";
|
|
268
|
+
}
|
|
269
|
+
// Returns ms elapsed since isolation began
|
|
270
|
+
getIsolationDurationMs() {
|
|
271
|
+
if (this.state.isolatedAt === void 0) return 0;
|
|
272
|
+
return Date.now() - this.state.isolatedAt;
|
|
273
|
+
}
|
|
274
|
+
// ============================================================
|
|
275
|
+
// PRIVATE
|
|
276
|
+
// ============================================================
|
|
277
|
+
startGraceTimer() {
|
|
278
|
+
this.graceTimer = setTimeout(() => {
|
|
279
|
+
const isHighRisk = this.riskTier === "HIGH" || this.riskTier === "CRITICAL";
|
|
280
|
+
const nextTier = isHighRisk ? "GRACE_HIGH" : "GRACE_STD";
|
|
281
|
+
this.transition(nextTier);
|
|
282
|
+
}, this.gracePeriodMs);
|
|
283
|
+
}
|
|
284
|
+
transition(to) {
|
|
285
|
+
const from = this.state.tier;
|
|
286
|
+
this.state.tier = to;
|
|
287
|
+
if (to === "ISOLATED") {
|
|
288
|
+
this.state.isolatedAt = Date.now();
|
|
289
|
+
}
|
|
290
|
+
if (to === "GRACE_STD" || to === "GRACE_HIGH") {
|
|
291
|
+
this.state.graceElapsedAt = Date.now();
|
|
292
|
+
}
|
|
293
|
+
this.onTierChange?.(from, to);
|
|
294
|
+
if (to === "GRACE_HIGH") {
|
|
295
|
+
console.error(
|
|
296
|
+
"[Mandate] GRACE_HIGH: Agent paused. Human intervention required."
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/hooks/openai.ts
|
|
303
|
+
var MandateOpenAIHook = class {
|
|
304
|
+
constructor(config, evaluator, buffer, degradation) {
|
|
305
|
+
this.config = config;
|
|
306
|
+
this.evaluator = evaluator;
|
|
307
|
+
this.buffer = buffer;
|
|
308
|
+
this.degradation = degradation;
|
|
309
|
+
this.sessionId = this.generateSessionId();
|
|
310
|
+
}
|
|
311
|
+
// Intercept OpenAI tool calls before execution
|
|
312
|
+
async interceptToolCalls(toolCalls, stepInChain = 0) {
|
|
313
|
+
const allowed = [];
|
|
314
|
+
const blocked = [];
|
|
315
|
+
for (const toolCall of toolCalls) {
|
|
316
|
+
const intent = this.buildIntent(toolCall, stepInChain);
|
|
317
|
+
const result = this.evaluator.evaluate(intent);
|
|
318
|
+
this.logToBuffer(intent, result);
|
|
319
|
+
if (result.decision === "ALLOW") {
|
|
320
|
+
allowed.push(toolCall);
|
|
321
|
+
} else if (result.decision === "THROTTLE") {
|
|
322
|
+
await this.delay(2e3);
|
|
323
|
+
this.config.onAlert?.(intent, result.anomalyScore ?? 0);
|
|
324
|
+
allowed.push(toolCall);
|
|
325
|
+
} else {
|
|
326
|
+
blocked.push({ toolCall, result });
|
|
327
|
+
this.config.onViolation?.(intent, result);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return { allowed, blocked };
|
|
331
|
+
}
|
|
332
|
+
// ============================================================
|
|
333
|
+
// PRIVATE
|
|
334
|
+
// ============================================================
|
|
335
|
+
buildIntent(toolCall, stepInChain) {
|
|
336
|
+
let args = {};
|
|
337
|
+
try {
|
|
338
|
+
const parsed = JSON.parse(toolCall.function.arguments);
|
|
339
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
340
|
+
args = parsed;
|
|
341
|
+
}
|
|
342
|
+
} catch {
|
|
343
|
+
args = { raw: toolCall.function.arguments };
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
toolName: toolCall.function.name,
|
|
347
|
+
args,
|
|
348
|
+
context: {
|
|
349
|
+
agentId: this.config.agentId,
|
|
350
|
+
sessionId: this.sessionId,
|
|
351
|
+
environment: this.config.environment,
|
|
352
|
+
stepInChain,
|
|
353
|
+
framework: "openai-assistants",
|
|
354
|
+
timestamp: Date.now()
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
logToBuffer(intent, result) {
|
|
359
|
+
if (this.config.auditLevel === "off") return;
|
|
360
|
+
this.buffer.append({
|
|
361
|
+
timestamp: Date.now(),
|
|
362
|
+
source: "REALTIME",
|
|
363
|
+
agentId: this.config.agentId,
|
|
364
|
+
orgId: this.config.orgId,
|
|
365
|
+
policyHash: this.config.policy.policyHash,
|
|
366
|
+
degradationTier: this.degradation.getCurrentTier(),
|
|
367
|
+
toolName: intent.toolName,
|
|
368
|
+
toolArgs: this.config.auditLevel === "full" ? intent.args : {},
|
|
369
|
+
intentContext: intent.context,
|
|
370
|
+
anomalyScore: result.anomalyScore ?? 0,
|
|
371
|
+
policyDecision: result.decision,
|
|
372
|
+
responseLevel: 1,
|
|
373
|
+
evalLatencyMs: result.latencyMs
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
generateSessionId() {
|
|
377
|
+
return `sess_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
378
|
+
}
|
|
379
|
+
delay(ms) {
|
|
380
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// src/hooks/langchain.ts
|
|
385
|
+
var MandateLangChainHook = class {
|
|
386
|
+
constructor(config, evaluator, buffer, degradation) {
|
|
387
|
+
this.stepCounter = 0;
|
|
388
|
+
this.config = config;
|
|
389
|
+
this.evaluator = evaluator;
|
|
390
|
+
this.buffer = buffer;
|
|
391
|
+
this.degradation = degradation;
|
|
392
|
+
this.sessionId = `sess_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
393
|
+
}
|
|
394
|
+
// Returns a before_tool_call callback for LangChain
|
|
395
|
+
// Usage: agent.beforeToolCall = mandateHook.getBeforeToolCallHook()
|
|
396
|
+
getBeforeToolCallHook() {
|
|
397
|
+
return async (toolCall) => {
|
|
398
|
+
const step = this.stepCounter++;
|
|
399
|
+
const intent = this.buildIntent(toolCall, step);
|
|
400
|
+
const result = this.evaluator.evaluate(intent);
|
|
401
|
+
this.logToBuffer(intent, result);
|
|
402
|
+
if (result.decision === "ALLOW") {
|
|
403
|
+
return toolCall;
|
|
404
|
+
}
|
|
405
|
+
if (result.decision === "THROTTLE") {
|
|
406
|
+
await this.delay(2e3);
|
|
407
|
+
this.config.onAlert?.(intent, result.anomalyScore ?? 0);
|
|
408
|
+
return toolCall;
|
|
409
|
+
}
|
|
410
|
+
this.config.onViolation?.(intent, result);
|
|
411
|
+
return null;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// Direct interception for manual use
|
|
415
|
+
async interceptToolCall(toolCall) {
|
|
416
|
+
const step = this.stepCounter++;
|
|
417
|
+
const intent = this.buildIntent(toolCall, step);
|
|
418
|
+
const result = this.evaluator.evaluate(intent);
|
|
419
|
+
this.logToBuffer(intent, result);
|
|
420
|
+
if (result.decision === "DENY" || result.decision === "ESCALATE") {
|
|
421
|
+
this.config.onViolation?.(intent, result);
|
|
422
|
+
return { allowed: false, result };
|
|
423
|
+
}
|
|
424
|
+
if (result.decision === "THROTTLE") {
|
|
425
|
+
await this.delay(2e3);
|
|
426
|
+
this.config.onAlert?.(intent, result.anomalyScore ?? 0);
|
|
427
|
+
}
|
|
428
|
+
return { allowed: true, result };
|
|
429
|
+
}
|
|
430
|
+
// ============================================================
|
|
431
|
+
// PRIVATE
|
|
432
|
+
// ============================================================
|
|
433
|
+
buildIntent(toolCall, step) {
|
|
434
|
+
return {
|
|
435
|
+
toolName: toolCall.name,
|
|
436
|
+
args: toolCall.args,
|
|
437
|
+
context: {
|
|
438
|
+
agentId: this.config.agentId,
|
|
439
|
+
sessionId: this.sessionId,
|
|
440
|
+
environment: this.config.environment,
|
|
441
|
+
stepInChain: step,
|
|
442
|
+
framework: "langchain",
|
|
443
|
+
timestamp: Date.now()
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
logToBuffer(intent, result) {
|
|
448
|
+
if (this.config.auditLevel === "off") return;
|
|
449
|
+
this.buffer.append({
|
|
450
|
+
timestamp: Date.now(),
|
|
451
|
+
source: "REALTIME",
|
|
452
|
+
agentId: this.config.agentId,
|
|
453
|
+
orgId: this.config.orgId,
|
|
454
|
+
policyHash: this.config.policy.policyHash,
|
|
455
|
+
degradationTier: this.degradation.getCurrentTier(),
|
|
456
|
+
toolName: intent.toolName,
|
|
457
|
+
toolArgs: this.config.auditLevel === "full" ? intent.args : {},
|
|
458
|
+
intentContext: intent.context,
|
|
459
|
+
anomalyScore: result.anomalyScore ?? 0,
|
|
460
|
+
policyDecision: result.decision,
|
|
461
|
+
responseLevel: 1,
|
|
462
|
+
evalLatencyMs: result.latencyMs
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
delay(ms) {
|
|
466
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// src/hooks/anthropic.ts
|
|
471
|
+
var MandateAnthropicHook = class {
|
|
472
|
+
constructor(config, evaluator, buffer, degradation) {
|
|
473
|
+
this.stepCounter = 0;
|
|
474
|
+
this.config = config;
|
|
475
|
+
this.evaluator = evaluator;
|
|
476
|
+
this.buffer = buffer;
|
|
477
|
+
this.degradation = degradation;
|
|
478
|
+
this.sessionId = `sess_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
479
|
+
}
|
|
480
|
+
// Intercept Anthropic tool_use content blocks
|
|
481
|
+
// Call this after receiving a response with tool_use blocks
|
|
482
|
+
// before executing any tools
|
|
483
|
+
async interceptToolUse(toolUseBlocks) {
|
|
484
|
+
const allowed = [];
|
|
485
|
+
const blocked = [];
|
|
486
|
+
for (const block of toolUseBlocks) {
|
|
487
|
+
const step = this.stepCounter++;
|
|
488
|
+
const intent = this.buildIntent(block, step);
|
|
489
|
+
const result = this.evaluator.evaluate(intent);
|
|
490
|
+
this.logToBuffer(intent, result);
|
|
491
|
+
if (result.decision === "ALLOW") {
|
|
492
|
+
allowed.push(block);
|
|
493
|
+
} else if (result.decision === "THROTTLE") {
|
|
494
|
+
await this.delay(2e3);
|
|
495
|
+
this.config.onAlert?.(intent, result.anomalyScore ?? 0);
|
|
496
|
+
allowed.push(block);
|
|
497
|
+
} else {
|
|
498
|
+
blocked.push({ block, result });
|
|
499
|
+
this.config.onViolation?.(intent, result);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return { allowed, blocked };
|
|
503
|
+
}
|
|
504
|
+
// ============================================================
|
|
505
|
+
// PRIVATE
|
|
506
|
+
// ============================================================
|
|
507
|
+
buildIntent(block, step) {
|
|
508
|
+
return {
|
|
509
|
+
toolName: block.name,
|
|
510
|
+
args: block.input,
|
|
511
|
+
context: {
|
|
512
|
+
agentId: this.config.agentId,
|
|
513
|
+
sessionId: this.sessionId,
|
|
514
|
+
environment: this.config.environment,
|
|
515
|
+
stepInChain: step,
|
|
516
|
+
framework: "anthropic",
|
|
517
|
+
timestamp: Date.now()
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
logToBuffer(intent, result) {
|
|
522
|
+
if (this.config.auditLevel === "off") return;
|
|
523
|
+
this.buffer.append({
|
|
524
|
+
timestamp: Date.now(),
|
|
525
|
+
source: "REALTIME",
|
|
526
|
+
agentId: this.config.agentId,
|
|
527
|
+
orgId: this.config.orgId,
|
|
528
|
+
policyHash: this.config.policy.policyHash,
|
|
529
|
+
degradationTier: this.degradation.getCurrentTier(),
|
|
530
|
+
toolName: intent.toolName,
|
|
531
|
+
toolArgs: this.config.auditLevel === "full" ? intent.args : {},
|
|
532
|
+
intentContext: intent.context,
|
|
533
|
+
anomalyScore: result.anomalyScore ?? 0,
|
|
534
|
+
policyDecision: result.decision,
|
|
535
|
+
responseLevel: 1,
|
|
536
|
+
evalLatencyMs: result.latencyMs
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
delay(ms) {
|
|
540
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/index.ts
|
|
545
|
+
var MandateAgent = class {
|
|
546
|
+
constructor(config) {
|
|
547
|
+
this.config = config;
|
|
548
|
+
this.evaluator = new JSPolicyEvaluator(config.policy);
|
|
549
|
+
this.buffer = new AuditBuffer({
|
|
550
|
+
maxSize: 1e4,
|
|
551
|
+
flushCallback: async (events) => {
|
|
552
|
+
await this.sendToControlPlane(events);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
this.degradation = new DegradationManager({
|
|
556
|
+
gracePeriodMs: config.policy.localAutonomy.gracePeriodMs,
|
|
557
|
+
riskTier: config.riskTier ?? "STANDARD",
|
|
558
|
+
onTierChange: (from, to) => {
|
|
559
|
+
config.onDegradation?.(from, to);
|
|
560
|
+
console.warn(`[Mandate] Degradation: ${from} \u2192 ${to}`);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
this.openai = new MandateOpenAIHook(
|
|
564
|
+
config,
|
|
565
|
+
this.evaluator,
|
|
566
|
+
this.buffer,
|
|
567
|
+
this.degradation
|
|
568
|
+
);
|
|
569
|
+
this.langchain = new MandateLangChainHook(
|
|
570
|
+
config,
|
|
571
|
+
this.evaluator,
|
|
572
|
+
this.buffer,
|
|
573
|
+
this.degradation
|
|
574
|
+
);
|
|
575
|
+
this.anthropic = new MandateAnthropicHook(
|
|
576
|
+
config,
|
|
577
|
+
this.evaluator,
|
|
578
|
+
this.buffer,
|
|
579
|
+
this.degradation
|
|
580
|
+
);
|
|
581
|
+
console.log(
|
|
582
|
+
`[Mandate] Agent "${config.agentId}" initialized | env: ${config.environment} | framework: ${config.framework} | policy: ${config.policy.policyHash}`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
// Direct evaluation for custom frameworks
|
|
586
|
+
evaluate(intent) {
|
|
587
|
+
return this.evaluator.evaluate(intent);
|
|
588
|
+
}
|
|
589
|
+
// Flush the audit buffer manually
|
|
590
|
+
async flushAuditBuffer() {
|
|
591
|
+
return this.buffer.flush();
|
|
592
|
+
}
|
|
593
|
+
// Verify the cryptographic integrity of the audit chain
|
|
594
|
+
verifyAuditChain() {
|
|
595
|
+
return this.buffer.verify();
|
|
596
|
+
}
|
|
597
|
+
// Get current degradation tier
|
|
598
|
+
getDegradationTier() {
|
|
599
|
+
return this.degradation.getCurrentTier();
|
|
600
|
+
}
|
|
601
|
+
// Get current audit buffer size
|
|
602
|
+
getBufferSize() {
|
|
603
|
+
return this.buffer.size();
|
|
604
|
+
}
|
|
605
|
+
// Update policy — hot reload, no restart required
|
|
606
|
+
updatePolicy(policy) {
|
|
607
|
+
this.evaluator.updatePolicy(policy);
|
|
608
|
+
console.log(`[Mandate] Policy updated: ${policy.policyHash}`);
|
|
609
|
+
}
|
|
610
|
+
// ============================================================
|
|
611
|
+
// PRIVATE
|
|
612
|
+
// ============================================================
|
|
613
|
+
async sendToControlPlane(events) {
|
|
614
|
+
if (!this.config.controlPlaneUrl || !this.config.apiKey) return;
|
|
615
|
+
try {
|
|
616
|
+
const response = await fetch(
|
|
617
|
+
`${this.config.controlPlaneUrl}/v1/events`,
|
|
618
|
+
{
|
|
619
|
+
method: "POST",
|
|
620
|
+
headers: {
|
|
621
|
+
"Content-Type": "application/json",
|
|
622
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
623
|
+
},
|
|
624
|
+
body: JSON.stringify({ events })
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
throw new Error(`Control plane responded with ${response.status}`);
|
|
629
|
+
}
|
|
630
|
+
this.degradation.onControlPlaneReconnected();
|
|
631
|
+
} catch {
|
|
632
|
+
this.degradation.onControlPlaneUnreachable();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
function createMandateAgent(config) {
|
|
637
|
+
return new MandateAgent(config);
|
|
638
|
+
}
|
|
639
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
640
|
+
0 && (module.exports = {
|
|
641
|
+
MandateAgent,
|
|
642
|
+
createMandateAgent
|
|
643
|
+
});
|