@tyvm/knowhow 0.0.21 → 0.0.22

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.
Files changed (69) hide show
  1. package/package.json +3 -1
  2. package/src/agents/tools/executeScript/README.md +78 -0
  3. package/src/agents/tools/executeScript/definition.ts +73 -0
  4. package/src/agents/tools/executeScript/examples/quick-test.ts +80 -0
  5. package/src/agents/tools/executeScript/examples/serialization-test.ts +309 -0
  6. package/src/agents/tools/executeScript/examples/test-runner.ts +204 -0
  7. package/src/agents/tools/executeScript/index.ts +74 -0
  8. package/src/agents/tools/index.ts +1 -0
  9. package/src/agents/tools/list.ts +2 -1
  10. package/src/cli.ts +2 -6
  11. package/src/clients/index.ts +23 -9
  12. package/src/services/Tools.ts +19 -3
  13. package/src/services/script-execution/SandboxContext.ts +278 -0
  14. package/src/services/script-execution/ScriptExecutor.ts +337 -0
  15. package/src/services/script-execution/ScriptPolicy.ts +236 -0
  16. package/src/services/script-execution/ScriptTracer.ts +249 -0
  17. package/src/services/script-execution/types.ts +134 -0
  18. package/ts_build/src/agents/tools/executeScript/definition.d.ts +2 -0
  19. package/ts_build/src/agents/tools/executeScript/definition.js +70 -0
  20. package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -0
  21. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +3 -0
  22. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +68 -0
  23. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +1 -0
  24. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +15 -0
  25. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +267 -0
  26. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +1 -0
  27. package/ts_build/src/agents/tools/executeScript/examples/simple-example.d.ts +20 -0
  28. package/ts_build/src/agents/tools/executeScript/examples/simple-example.js +35 -0
  29. package/ts_build/src/agents/tools/executeScript/examples/simple-example.js.map +1 -0
  30. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +4 -0
  31. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +202 -0
  32. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +1 -0
  33. package/ts_build/src/agents/tools/executeScript/handler.d.ts +27 -0
  34. package/ts_build/src/agents/tools/executeScript/handler.js +64 -0
  35. package/ts_build/src/agents/tools/executeScript/handler.js.map +1 -0
  36. package/ts_build/src/agents/tools/executeScript/index.d.ts +27 -0
  37. package/ts_build/src/agents/tools/executeScript/index.js +64 -0
  38. package/ts_build/src/agents/tools/executeScript/index.js.map +1 -0
  39. package/ts_build/src/agents/tools/executeScript.d.ts +29 -0
  40. package/ts_build/src/agents/tools/executeScript.js +124 -0
  41. package/ts_build/src/agents/tools/executeScript.js.map +1 -0
  42. package/ts_build/src/agents/tools/index.d.ts +1 -0
  43. package/ts_build/src/agents/tools/index.js +1 -0
  44. package/ts_build/src/agents/tools/index.js.map +1 -1
  45. package/ts_build/src/agents/tools/list.js +2 -0
  46. package/ts_build/src/agents/tools/list.js.map +1 -1
  47. package/ts_build/src/cli.js +2 -6
  48. package/ts_build/src/cli.js.map +1 -1
  49. package/ts_build/src/clients/index.d.ts +9 -2
  50. package/ts_build/src/clients/index.js +17 -4
  51. package/ts_build/src/clients/index.js.map +1 -1
  52. package/ts_build/src/services/Tools.d.ts +3 -0
  53. package/ts_build/src/services/Tools.js +10 -2
  54. package/ts_build/src/services/Tools.js.map +1 -1
  55. package/ts_build/src/services/script-execution/SandboxContext.d.ts +34 -0
  56. package/ts_build/src/services/script-execution/SandboxContext.js +188 -0
  57. package/ts_build/src/services/script-execution/SandboxContext.js.map +1 -0
  58. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +17 -0
  59. package/ts_build/src/services/script-execution/ScriptExecutor.js +207 -0
  60. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -0
  61. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +27 -0
  62. package/ts_build/src/services/script-execution/ScriptPolicy.js +150 -0
  63. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -0
  64. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +19 -0
  65. package/ts_build/src/services/script-execution/ScriptTracer.js +186 -0
  66. package/ts_build/src/services/script-execution/ScriptTracer.js.map +1 -0
  67. package/ts_build/src/services/script-execution/types.d.ts +108 -0
  68. package/ts_build/src/services/script-execution/types.js +3 -0
  69. package/ts_build/src/services/script-execution/types.js.map +1 -0
@@ -0,0 +1,337 @@
1
+ import ivm from "isolated-vm";
2
+ import { Tools } from "../../services";
3
+ import { Clients } from "../../clients";
4
+ import { SandboxContext } from "./SandboxContext";
5
+ import { ScriptTracer } from "./ScriptTracer";
6
+ import { ScriptPolicyEnforcer } from "./ScriptPolicy";
7
+ import {
8
+ ExecutionRequest,
9
+ ExecutionResult,
10
+ ResourceQuotas,
11
+ SecurityPolicy,
12
+ ExecutionTrace,
13
+ } from "./types";
14
+
15
+ /**
16
+ * Executes TypeScript scripts in a secure sandbox environment
17
+ */
18
+ export class ScriptExecutor {
19
+ private defaultQuotas: ResourceQuotas = {
20
+ maxToolCalls: 50,
21
+ maxTokens: 10000,
22
+ maxExecutionTimeMs: 30000, // 30 seconds
23
+ maxCostUsd: 1.0,
24
+ maxMemoryMb: 100,
25
+ };
26
+
27
+ private defaultPolicy: SecurityPolicy = {
28
+ allowlistedTools: [], // Empty means all tools allowed
29
+ denylistedTools: [
30
+ "execCommand", // Dangerous system commands
31
+ "writeFileChunk", // File system write access
32
+ "patchFile", // File system modification
33
+ ],
34
+ maxScriptLength: 50000, // 50KB
35
+ allowNetworkAccess: false,
36
+ allowFileSystemAccess: false,
37
+ };
38
+
39
+ constructor(
40
+ private toolsService: typeof Tools | null = null,
41
+ private clients: typeof Clients | null = null
42
+ ) {}
43
+
44
+ /**
45
+ * Execute a TypeScript script in sandbox
46
+ */
47
+ async execute(request: ExecutionRequest): Promise<ExecutionResult> {
48
+ const tracer = new ScriptTracer();
49
+ const quotas = { ...this.defaultQuotas, ...request.quotas };
50
+ const policy = { ...this.defaultPolicy, ...request.policy };
51
+ const policyEnforcer = new ScriptPolicyEnforcer(quotas, policy);
52
+
53
+ tracer.emitEvent("execution_start", {
54
+ scriptLength: request.script.length,
55
+ quotas,
56
+ policy: {
57
+ ...policy,
58
+ // Don't log the full tool lists
59
+ allowlistedTools: `${policy.allowlistedTools.length} tools`,
60
+ denylistedTools: `${policy.denylistedTools.length} tools`,
61
+ },
62
+ });
63
+
64
+ try {
65
+ // Validate script
66
+ const validation = policyEnforcer.validateScript(request.script);
67
+ if (!validation.valid) {
68
+ tracer.emitEvent("script_validation_failed", {
69
+ issues: validation.issues,
70
+ });
71
+
72
+ return {
73
+ success: false,
74
+ error: `Script validation failed: ${validation.issues.join(", ")}`,
75
+ result: null,
76
+ trace: tracer.getTrace(),
77
+ artifacts: [],
78
+ consoleOutput: [],
79
+ };
80
+ }
81
+
82
+ tracer.emitEvent("script_validation_passed", {});
83
+
84
+ // Create sandbox context
85
+ const context = new SandboxContext(
86
+ this.toolsService,
87
+ this.clients,
88
+ tracer,
89
+ policyEnforcer
90
+ );
91
+
92
+ // Execute script with timeout
93
+ const startTime = Date.now();
94
+ const timeoutMs = quotas.maxExecutionTimeMs;
95
+
96
+ const result = await this.executeWithTimeout(
97
+ request.script,
98
+ context,
99
+ timeoutMs,
100
+ tracer,
101
+ policyEnforcer
102
+ );
103
+
104
+ const executionTime = Date.now() - startTime;
105
+ tracer.emitEvent("execution_complete", {
106
+ executionTimeMs: executionTime,
107
+ finalUsage: policyEnforcer.getUsage(),
108
+ });
109
+
110
+ return {
111
+ success: true,
112
+ error: null,
113
+ result,
114
+ trace: tracer.getTrace(),
115
+ artifacts: context.getArtifacts(),
116
+ consoleOutput: context.getConsoleOutput(),
117
+ };
118
+ } catch (error) {
119
+ const errorMessage =
120
+ error instanceof Error ? error.message : String(error);
121
+
122
+ tracer.emitEvent("execution_error", {
123
+ error: errorMessage,
124
+ finalUsage: policyEnforcer.getUsage(),
125
+ });
126
+
127
+ return {
128
+ success: false,
129
+ error: errorMessage,
130
+ result: null,
131
+ trace: tracer.getTrace(),
132
+ artifacts: [],
133
+ consoleOutput: [],
134
+ };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Execute script with timeout protection
140
+ */
141
+ private async executeWithTimeout(
142
+ script: string,
143
+ context: SandboxContext,
144
+ timeoutMs: number,
145
+ tracer: ScriptTracer,
146
+ policyEnforcer: ScriptPolicyEnforcer
147
+ ): Promise<any> {
148
+ return new Promise((resolve, reject) => {
149
+ const timeoutId = setTimeout(() => {
150
+ tracer.emitEvent("execution_timeout", { timeoutMs });
151
+ reject(new Error(`Script execution timed out after ${timeoutMs}ms`));
152
+ }, timeoutMs);
153
+
154
+ // Use isolated-vm for secure execution
155
+ this.executeScriptSecure(script, context, tracer, policyEnforcer)
156
+ .then((result) => {
157
+ clearTimeout(timeoutId);
158
+ resolve(result);
159
+ })
160
+ .catch((error) => {
161
+ clearTimeout(timeoutId);
162
+ reject(error);
163
+ });
164
+ });
165
+ }
166
+
167
+ /**
168
+ * Secure script execution using isolated-vm
169
+ */
170
+ private async executeScriptSecure(
171
+ script: string,
172
+ context: SandboxContext,
173
+ tracer: ScriptTracer,
174
+ policyEnforcer: ScriptPolicyEnforcer
175
+ ): Promise<any> {
176
+ tracer.emitEvent("secure_execution_start", {
177
+ note: "Using isolated-vm for secure execution",
178
+ });
179
+
180
+ // Create isolated VM instance with memory limit
181
+ const isolate = new ivm.Isolate({
182
+ memoryLimit: policyEnforcer.getQuotas().maxMemoryMb,
183
+ });
184
+
185
+ try {
186
+ // Create new context within the isolate
187
+ const vmContext = await isolate.createContext();
188
+
189
+ tracer.emitEvent("vm_context_created", {});
190
+
191
+ // Set up the global environment in the isolated context
192
+ await this.setupIsolatedContext(vmContext, context, tracer);
193
+
194
+ tracer.emitEvent("script_compilation_start", {});
195
+
196
+ // Compile the script
197
+ const wrappedScript = `
198
+ (async function() {
199
+ "use strict";
200
+ ${script}
201
+ })()
202
+ `;
203
+
204
+ const compiledScript = await isolate.compileScript(wrappedScript);
205
+
206
+ tracer.emitEvent("script_compilation_complete", {});
207
+ tracer.emitEvent("script_execution_start", {});
208
+
209
+ // Execute the script and get the result
210
+ const result = await compiledScript.run(vmContext, {
211
+ timeout: policyEnforcer.getQuotas().maxExecutionTimeMs,
212
+ promise: true,
213
+ });
214
+
215
+ tracer.emitEvent("script_execution_complete", {
216
+ resultType: typeof result,
217
+ });
218
+
219
+ return result;
220
+ } finally {
221
+ // Clean up the isolate
222
+ isolate.dispose();
223
+ tracer.emitEvent("vm_cleanup_complete", {});
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Set up the isolated context with safe globals and sandbox functions
229
+ */
230
+ private async setupIsolatedContext(
231
+ vmContext: ivm.Context,
232
+ sandboxContext: SandboxContext,
233
+ tracer: ScriptTracer
234
+ ): Promise<void> {
235
+ tracer.emitEvent("context_setup_start", {});
236
+
237
+ const globalRef = vmContext.global;
238
+ await globalRef.set("globalThis", globalRef.derefInto());
239
+
240
+ // Helper function to expose async host functions
241
+ const exposeAsync = async (
242
+ name: string,
243
+ fn: (...a: any[]) => Promise<any>
244
+ ) => {
245
+ await globalRef.set(
246
+ `__host_${name}`,
247
+ new ivm.Reference(async (...args: any[]) => {
248
+ const result = await fn(...args);
249
+ return new ivm.ExternalCopy(result).copyInto();
250
+ })
251
+ );
252
+ await vmContext.eval(`
253
+ globalThis.${name} = (...a) =>
254
+ __host_${name}.apply(undefined, a,
255
+ { arguments: { copy: true }, result: { promise: true, copy: true } });
256
+ `);
257
+ };
258
+
259
+ // Helper function to expose sync host functions
260
+ const exposeSync = async (name: string, fn: (...a: any[]) => any) => {
261
+ await globalRef.set(
262
+ `__host_${name}`,
263
+ new ivm.Reference((...args: any[]) => {
264
+ const result = fn(...args);
265
+ return new ivm.ExternalCopy(result).copyInto();
266
+ })
267
+ );
268
+ await vmContext.eval(`
269
+ globalThis.${name} = (...a) =>
270
+ __host_${name}.apply(undefined, a,
271
+ { arguments: { copy: true }, result: { copy: true } });
272
+ `);
273
+ };
274
+
275
+ // Expose async sandbox functions
276
+ await exposeAsync("callTool", (tool, params) =>
277
+ sandboxContext.callTool(tool as string, params)
278
+ );
279
+ await exposeAsync("llm", (messages, options) =>
280
+ sandboxContext.llm(messages, options || {})
281
+ );
282
+ await exposeAsync("sleep", (ms) => sandboxContext.sleep(ms));
283
+
284
+ // Expose sync sandbox functions
285
+ await exposeSync("createArtifact", (name, content, type) =>
286
+ sandboxContext.createArtifact(name as string, content, type)
287
+ );
288
+ await exposeSync("getQuotaUsage", () => sandboxContext.getQuotaUsage());
289
+
290
+ // Set up console bridging with individual function references
291
+ for (const level of ["log", "info", "warn", "error"] as const) {
292
+ await globalRef.set(
293
+ `__console_${level}`,
294
+ new ivm.Reference((...args: any[]) =>
295
+ sandboxContext.console[level](...args)
296
+ )
297
+ );
298
+ }
299
+ await vmContext.eval(`
300
+ globalThis.console = {};
301
+ for (const lvl of ["log", "info", "warn", "error"]) {
302
+ globalThis.console[lvl] = (...a) =>
303
+ globalThis["__console_" + lvl].apply(undefined, a,
304
+ { arguments: { copy: true } });
305
+ }
306
+ `);
307
+
308
+ tracer.emitEvent("context_setup_complete", {});
309
+ }
310
+
311
+ /**
312
+ * Legacy fallback execution method
313
+ */
314
+ private async executeScriptFallback(
315
+ script: string,
316
+ context: SandboxContext,
317
+ tracer: ScriptTracer,
318
+ policyEnforcer: ScriptPolicyEnforcer
319
+ ): Promise<any> {
320
+ // This is a fallback method that could use vm2 or other sandboxing
321
+ throw new Error("Isolated-vm execution failed, no fallback available");
322
+ }
323
+
324
+ /**
325
+ * Get default quotas
326
+ */
327
+ getDefaultQuotas(): ResourceQuotas {
328
+ return { ...this.defaultQuotas };
329
+ }
330
+
331
+ /**
332
+ * Get default policy
333
+ */
334
+ getDefaultPolicy(): SecurityPolicy {
335
+ return { ...this.defaultPolicy };
336
+ }
337
+ }
@@ -0,0 +1,236 @@
1
+ import {
2
+ ResourceQuotas,
3
+ SecurityPolicy,
4
+ QuotaUsage,
5
+ PolicyViolation
6
+ } from './types';
7
+
8
+ /**
9
+ * Enforces security policies and resource quotas for script execution
10
+ */
11
+ export class ScriptPolicyEnforcer {
12
+ private usage: QuotaUsage;
13
+ private violations: PolicyViolation[] = [];
14
+
15
+ constructor(
16
+ private quotas: ResourceQuotas,
17
+ private policy: SecurityPolicy
18
+ ) {
19
+ this.usage = {
20
+ toolCalls: 0,
21
+ tokens: 0,
22
+ executionTimeMs: 0,
23
+ costUsd: 0
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Check if a tool call is allowed
29
+ */
30
+ checkToolCall(toolName: string): boolean {
31
+ // Check if tool is in denylist
32
+ if (this.policy.denylistedTools && this.policy.denylistedTools.includes(toolName)) {
33
+ this.recordViolation('tool_denied', `Tool '${toolName}' is in denylist`);
34
+ return false;
35
+ }
36
+
37
+ // Check if tool is in allowlist (if allowlist is defined and not empty)
38
+ if (this.policy.allowlistedTools && this.policy.allowlistedTools.length > 0 &&
39
+ !this.policy.allowlistedTools.includes(toolName)) {
40
+ this.recordViolation('tool_not_allowed', `Tool '${toolName}' is not in allowlist`);
41
+ return false;
42
+ }
43
+
44
+ // Check quota
45
+ if (this.usage.toolCalls >= this.quotas.maxToolCalls) {
46
+ this.recordViolation('quota_exceeded', 'Maximum tool calls exceeded');
47
+ return false;
48
+ }
49
+
50
+ return true;
51
+ }
52
+
53
+ /**
54
+ * Record a tool call
55
+ */
56
+ recordToolCall(): void {
57
+ this.usage.toolCalls++;
58
+ }
59
+
60
+ /**
61
+ * Check if token usage is allowed
62
+ */
63
+ checkTokenUsage(tokens: number): boolean {
64
+ if (this.usage.tokens + tokens > this.quotas.maxTokens) {
65
+ this.recordViolation('quota_exceeded', 'Maximum tokens would be exceeded');
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+
71
+ /**
72
+ * Record token usage
73
+ */
74
+ recordTokenUsage(tokens: number): void {
75
+ this.usage.tokens += tokens;
76
+ }
77
+
78
+ /**
79
+ * Check if execution time limit is exceeded
80
+ */
81
+ checkExecutionTime(currentTimeMs: number): boolean {
82
+ if (currentTimeMs > this.quotas.maxExecutionTimeMs) {
83
+ this.recordViolation('quota_exceeded', 'Maximum execution time exceeded');
84
+ return false;
85
+ }
86
+ this.usage.executionTimeMs = currentTimeMs;
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Check if cost limit is exceeded
92
+ */
93
+ checkCost(additionalCost: number): boolean {
94
+ if (this.usage.costUsd + additionalCost > this.quotas.maxCostUsd) {
95
+ this.recordViolation('quota_exceeded', 'Maximum cost would be exceeded');
96
+ return false;
97
+ }
98
+ return true;
99
+ }
100
+
101
+ /**
102
+ * Record cost usage
103
+ */
104
+ recordCost(cost: number): void {
105
+ this.usage.costUsd += cost;
106
+ }
107
+
108
+ /**
109
+ * Get current usage
110
+ */
111
+ getUsage(): QuotaUsage {
112
+ return { ...this.usage };
113
+ }
114
+
115
+ /**
116
+ * Get current quotas
117
+ */
118
+ getQuotas(): ResourceQuotas {
119
+ return { ...this.quotas };
120
+ }
121
+
122
+ /**
123
+ * Get all policy violations
124
+ */
125
+ getViolations(): PolicyViolation[] {
126
+ return [...this.violations];
127
+ }
128
+
129
+ /**
130
+ * Check if there are any violations
131
+ */
132
+ hasViolations(): boolean {
133
+ return this.violations.length > 0;
134
+ }
135
+
136
+ /**
137
+ * Get the most recent violation
138
+ */
139
+ getLastViolation(): PolicyViolation | undefined {
140
+ return this.violations[this.violations.length - 1];
141
+ }
142
+
143
+ /**
144
+ * Reset usage counters
145
+ */
146
+ resetUsage(): void {
147
+ this.usage = {
148
+ toolCalls: 0,
149
+ tokens: 0,
150
+ executionTimeMs: 0,
151
+ costUsd: 0
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Reset violations
157
+ */
158
+ resetViolations(): void {
159
+ this.violations = [];
160
+ }
161
+
162
+ /**
163
+ * Validate script content for security issues
164
+ */
165
+ validateScript(scriptContent: string): { valid: boolean; issues: string[] } {
166
+ const issues: string[] = [];
167
+
168
+ // Check for dangerous patterns
169
+ const dangerousPatterns = [
170
+ /require\s*\(/gi, // Node.js require
171
+ /import\s+.*\s+from/gi, // ES6 imports (should be handled by bundler)
172
+ /process\./gi, // Process access
173
+ /global\./gi, // Global object access
174
+ /eval\s*\(/gi, // eval calls
175
+ /Function\s*\(/gi, // Function constructor
176
+ /setTimeout/gi, // setTimeout
177
+ /setInterval/gi, // setInterval
178
+ /fetch\s*\(/gi, // Direct fetch calls
179
+ /XMLHttpRequest/gi, // XHR
180
+ /WebSocket/gi, // WebSocket
181
+ /location\./gi, // Location object
182
+ /document\./gi, // Document object
183
+ /window\./gi, // Window object
184
+ ];
185
+
186
+ for (const pattern of dangerousPatterns) {
187
+ if (pattern.test(scriptContent)) {
188
+ issues.push(`Potentially dangerous pattern detected: ${pattern.source}`);
189
+ }
190
+ }
191
+
192
+ // Check script length
193
+ if (scriptContent.length > this.policy.maxScriptLength) {
194
+ issues.push(`Script too long: ${scriptContent.length} > ${this.policy.maxScriptLength}`);
195
+ }
196
+
197
+ // Check for excessive complexity (rough heuristic)
198
+ const complexityIndicators = [
199
+ /for\s*\(/gi,
200
+ /while\s*\(/gi,
201
+ /function\s+\w+/gi,
202
+ /=>\s*{/gi,
203
+ /if\s*\(/gi,
204
+ ];
205
+
206
+ let complexityScore = 0;
207
+ for (const indicator of complexityIndicators) {
208
+ const matches = scriptContent.match(indicator);
209
+ complexityScore += matches ? matches.length : 0;
210
+ }
211
+
212
+ if (complexityScore > 50) {
213
+ issues.push(`Script complexity too high: ${complexityScore} constructs detected`);
214
+ }
215
+
216
+ return {
217
+ valid: issues.length === 0,
218
+ issues
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Record a policy violation
224
+ */
225
+ private recordViolation(type: 'quota_exceeded' | 'tool_denied' | 'tool_not_allowed' | 'script_validation', message: string): void {
226
+ const violation: PolicyViolation = {
227
+ id: `violation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
228
+ type,
229
+ message,
230
+ timestamp: Date.now(),
231
+ usage: { ...this.usage }
232
+ };
233
+
234
+ this.violations.push(violation);
235
+ }
236
+ }