@directive-run/claude-plugin 1.16.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.
Files changed (83) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE +26 -0
  3. package/README.md +113 -0
  4. package/dist/index.cjs +2 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +66 -0
  7. package/dist/index.d.ts +66 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +64 -0
  11. package/skills/README.md +9 -0
  12. package/skills/building-ai-agents/SKILL.md +414 -0
  13. package/skills/building-ai-agents/ai-adapters.md +257 -0
  14. package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
  15. package/skills/building-ai-agents/ai-communication.md +304 -0
  16. package/skills/building-ai-agents/api-skeleton.md +5 -0
  17. package/skills/building-ai-agents/examples.md +849 -0
  18. package/skills/building-ai-orchestrators/SKILL.md +401 -0
  19. package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
  20. package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
  21. package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
  22. package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
  23. package/skills/building-ai-orchestrators/examples.md +1609 -0
  24. package/skills/building-directive-systems/SKILL.md +499 -0
  25. package/skills/building-directive-systems/api-skeleton.md +5 -0
  26. package/skills/building-directive-systems/examples.md +4149 -0
  27. package/skills/building-directive-systems/multi-module.md +327 -0
  28. package/skills/building-directive-systems/plugins.md +361 -0
  29. package/skills/building-directive-systems/react-adapter.md +376 -0
  30. package/skills/building-directive-systems/system-api.md +463 -0
  31. package/skills/getting-started-with-directive/SKILL.md +198 -0
  32. package/skills/getting-started-with-directive/api-skeleton.md +5 -0
  33. package/skills/getting-started-with-directive/core-patterns.md +240 -0
  34. package/skills/getting-started-with-directive/examples.md +87 -0
  35. package/skills/getting-started-with-directive/sitemap.md +202 -0
  36. package/skills/hardening-ai-systems/SKILL.md +377 -0
  37. package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
  38. package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
  39. package/skills/hardening-ai-systems/ai-security.md +400 -0
  40. package/skills/hardening-ai-systems/api-skeleton.md +5 -0
  41. package/skills/hardening-ai-systems/examples.md +628 -0
  42. package/skills/migrating-to-directive/SKILL.md +249 -0
  43. package/skills/migrating-to-directive/anti-patterns.md +382 -0
  44. package/skills/migrating-to-directive/api-skeleton.md +5 -0
  45. package/skills/migrating-to-directive/core-patterns.md +240 -0
  46. package/skills/migrating-to-directive/examples.md +511 -0
  47. package/skills/migrating-to-directive/schema-types.md +273 -0
  48. package/skills/reviewing-directive-code/SKILL.md +232 -0
  49. package/skills/reviewing-directive-code/anti-patterns.md +382 -0
  50. package/skills/reviewing-directive-code/api-skeleton.md +5 -0
  51. package/skills/reviewing-directive-code/core-patterns.md +240 -0
  52. package/skills/reviewing-directive-code/examples.md +463 -0
  53. package/skills/reviewing-directive-code/naming.md +321 -0
  54. package/skills/scaffolding-directive-modules/SKILL.md +218 -0
  55. package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
  56. package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
  57. package/skills/scaffolding-directive-modules/examples.md +1386 -0
  58. package/skills/scaffolding-directive-modules/naming.md +321 -0
  59. package/skills/scaffolding-directive-modules/schema-types.md +273 -0
  60. package/skills/testing-ai-systems/SKILL.md +398 -0
  61. package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
  62. package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
  63. package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
  64. package/skills/testing-ai-systems/api-skeleton.md +5 -0
  65. package/skills/testing-ai-systems/examples.md +1261 -0
  66. package/skills/testing-directive-code/SKILL.md +479 -0
  67. package/skills/testing-directive-code/api-skeleton.md +5 -0
  68. package/skills/testing-directive-code/examples.md +156 -0
  69. package/skills/testing-directive-code/history.md +351 -0
  70. package/skills/testing-directive-code/testing.md +412 -0
  71. package/skills/writing-directive-constraints/SKILL.md +486 -0
  72. package/skills/writing-directive-constraints/api-skeleton.md +5 -0
  73. package/skills/writing-directive-constraints/constraints.md +274 -0
  74. package/skills/writing-directive-constraints/error-boundaries.md +330 -0
  75. package/skills/writing-directive-constraints/examples.md +1361 -0
  76. package/skills/writing-directive-constraints/resolvers.md +367 -0
  77. package/skills/writing-directive-modules/SKILL.md +342 -0
  78. package/skills/writing-directive-modules/anti-patterns.md +382 -0
  79. package/skills/writing-directive-modules/api-skeleton.md +5 -0
  80. package/skills/writing-directive-modules/core-patterns.md +240 -0
  81. package/skills/writing-directive-modules/examples.md +1639 -0
  82. package/skills/writing-directive-modules/naming.md +321 -0
  83. package/skills/writing-directive-modules/schema-types.md +273 -0
@@ -0,0 +1,628 @@
1
+ # Examples
2
+
3
+ > Auto-generated from extracted examples. Do not edit manually.
4
+
5
+ ## ai-guardrails
6
+
7
+ ```typescript
8
+ // Example: ai-guardrails
9
+ // Source: examples/ai-guardrails/src/module.ts
10
+ // Pure module file — no DOM wiring
11
+
12
+ /**
13
+ * AI Safety Shield — Directive Module
14
+ *
15
+ * Types, schema, module definition, timeline, analysis functions,
16
+ * and system creation for prompt injection & PII detection guardrails.
17
+ */
18
+
19
+ import {
20
+ type InjectionDetectionResult,
21
+ type PIIDetectionResult,
22
+ detectAndRedactPII,
23
+ detectPII,
24
+ detectPromptInjection,
25
+ } from "@directive-run/ai";
26
+ import {
27
+ type ModuleSchema,
28
+ createModule,
29
+ createSystem,
30
+ t,
31
+ } from "@directive-run/core";
32
+ import { devtoolsPlugin, emitDevToolsEvent } from "@directive-run/core/plugins";
33
+
34
+ // ============================================================================
35
+ // Types
36
+ // ============================================================================
37
+
38
+ export interface ChatMessage {
39
+ id: string;
40
+ text: string;
41
+ blocked: boolean;
42
+ redactedText: string;
43
+ injectionResult: InjectionDetectionResult | null;
44
+ piiResult: PIIDetectionResult | null;
45
+ }
46
+
47
+ export interface TimelineEntry {
48
+ time: number;
49
+ event: string;
50
+ detail: string;
51
+ type: "pass" | "injection" | "pii" | "compliance" | "info";
52
+ }
53
+
54
+ export type ComplianceMode = "standard" | "gdpr" | "hipaa";
55
+
56
+ // ============================================================================
57
+ // Timeline
58
+ // ============================================================================
59
+
60
+ export const timeline: TimelineEntry[] = [];
61
+
62
+ export function addTimeline(
63
+ event: string,
64
+ detail: string,
65
+ type: TimelineEntry["type"],
66
+ ) {
67
+ timeline.unshift({ time: Date.now(), event, detail, type });
68
+ if (timeline.length > 50) {
69
+ timeline.length = 50;
70
+ }
71
+ }
72
+
73
+ // ============================================================================
74
+ // Schema
75
+ // ============================================================================
76
+
77
+ export const schema = {
78
+ facts: {
79
+ messages: t.array<ChatMessage>(),
80
+ complianceMode: t.string<ComplianceMode>(),
81
+ redactionEnabled: t.boolean(),
82
+ blockedCount: t.number(),
83
+ injectionAttempts: t.number(),
84
+ piiDetections: t.number(),
85
+ complianceBlocks: t.number(),
86
+ },
87
+ derivations: {
88
+ messageCount: t.number(),
89
+ blockRate: t.string(),
90
+ piiTypeCounts: t.string(),
91
+ },
92
+ events: {
93
+ setComplianceMode: { value: t.string() },
94
+ toggleRedaction: {},
95
+ clearHistory: {},
96
+ },
97
+ requirements: {},
98
+ } satisfies ModuleSchema;
99
+
100
+ // ============================================================================
101
+ // Module
102
+ // ============================================================================
103
+
104
+ const guardrailModule = createModule("guardrails", {
105
+ schema,
106
+
107
+ init: (facts) => {
108
+ facts.messages = [];
109
+ facts.complianceMode = "standard";
110
+ facts.redactionEnabled = true;
111
+ facts.blockedCount = 0;
112
+ facts.injectionAttempts = 0;
113
+ facts.piiDetections = 0;
114
+ facts.complianceBlocks = 0;
115
+ },
116
+
117
+ derive: {
118
+ messageCount: (facts) => facts.messages.length,
119
+ blockRate: (facts) => {
120
+ if (facts.messages.length === 0) {
121
+ return "0%";
122
+ }
123
+
124
+ const blocked = (facts.messages as ChatMessage[]).filter(
125
+ (m) => m.blocked,
126
+ ).length;
127
+
128
+ return `${Math.round((blocked / facts.messages.length) * 100)}%`;
129
+ },
130
+ piiTypeCounts: (facts) => {
131
+ const counts: Record<string, number> = {};
132
+ for (const msg of facts.messages as ChatMessage[]) {
133
+ if (msg.piiResult?.detected) {
134
+ for (const item of msg.piiResult.items) {
135
+ counts[item.type] = (counts[item.type] ?? 0) + 1;
136
+ }
137
+ }
138
+ }
139
+
140
+ return (
141
+ Object.entries(counts)
142
+ .map(([k, v]) => `${k}:${v}`)
143
+ .join(", ") || "none"
144
+ );
145
+ },
146
+ },
147
+
148
+ events: {
149
+ setComplianceMode: (facts, { value }) => {
150
+ facts.complianceMode = value as ComplianceMode;
151
+ },
152
+ toggleRedaction: (facts) => {
153
+ facts.redactionEnabled = !facts.redactionEnabled;
154
+ },
155
+ clearHistory: (facts) => {
156
+ facts.messages = [];
157
+ facts.blockedCount = 0;
158
+ facts.injectionAttempts = 0;
159
+ facts.piiDetections = 0;
160
+ facts.complianceBlocks = 0;
161
+ timeline.length = 0;
162
+ },
163
+ },
164
+ });
165
+
166
+ // ============================================================================
167
+ // System
168
+ // ============================================================================
169
+
170
+ export const system = createSystem({
171
+ module: guardrailModule,
172
+ trace: true,
173
+ plugins: [devtoolsPlugin({ name: "ai-guardrails" })],
174
+ });
175
+
176
+ // ============================================================================
177
+ // Analysis Functions
178
+ // ============================================================================
179
+
180
+ /**
181
+ * Compliance gate: blocks the message when the active mode forbids the kinds
182
+ * of PII detected. Emits its own devtools event and returns whether it blocked.
183
+ */
184
+ function runComplianceCheck(
185
+ piiResult: PIIDetectionResult,
186
+ textLength: number,
187
+ ): boolean {
188
+ const mode = system.facts.complianceMode as ComplianceMode;
189
+ let blocked = false;
190
+
191
+ if (mode !== "standard" && piiResult.detected) {
192
+ // HIPAA Safe Harbor (45 CFR 164.514) enumerates 18 identifier classes —
193
+ // names, geographic data, dates, contact info, account/certificate/license
194
+ // numbers, IP addresses, and more. That is effectively every PII type this
195
+ // detector emits, so under HIPAA *any* detected PII is PHI and blocks.
196
+ const hasPHI = piiResult.detected;
197
+ const hasContactInfo = piiResult.items.some(
198
+ (i) =>
199
+ i.type === "email" ||
200
+ i.type === "phone" ||
201
+ i.type === "name" ||
202
+ i.type === "date_of_birth" ||
203
+ i.type === "address",
204
+ );
205
+
206
+ if (mode === "hipaa" && hasPHI) {
207
+ blocked = true;
208
+ system.facts.complianceBlocks =
209
+ (system.facts.complianceBlocks as number) + 1;
210
+ addTimeline("compliance", "HIPAA: PHI detected", "compliance");
211
+ }
212
+
213
+ if (mode === "gdpr" && hasContactInfo) {
214
+ blocked = true;
215
+ system.facts.complianceBlocks =
216
+ (system.facts.complianceBlocks as number) + 1;
217
+ addTimeline("compliance", "GDPR: personal data detected", "compliance");
218
+ }
219
+ }
220
+
221
+ emitDevToolsEvent({
222
+ type: "guardrail_check",
223
+ guardrailName: `compliance-${mode}`,
224
+ guardrailType: "input",
225
+ passed: !blocked || !piiResult.detected,
226
+ inputLength: textLength,
227
+ });
228
+
229
+ return blocked;
230
+ }
231
+
232
+ /**
233
+ * When redaction is on, never persist raw PII into reactive facts: strip the
234
+ * plaintext `value` from each detected item before it reaches facts.
235
+ */
236
+ function toSafePiiResult(
237
+ piiResult: PIIDetectionResult,
238
+ redactionEnabled: boolean,
239
+ ): PIIDetectionResult | null {
240
+ if (!piiResult.detected) {
241
+ return null;
242
+ }
243
+
244
+ if (!redactionEnabled) {
245
+ return piiResult;
246
+ }
247
+
248
+ return {
249
+ ...piiResult,
250
+ items: piiResult.items.map((item) => ({
251
+ ...item,
252
+ value: "[redacted]",
253
+ })),
254
+ };
255
+ }
256
+
257
+ export async function analyzeMessage(text: string): Promise<ChatMessage> {
258
+ const id = `msg-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
259
+ let blocked = false;
260
+
261
+ // 1. Prompt injection detection
262
+ const injectionResult = detectPromptInjection(text);
263
+ if (injectionResult.detected) {
264
+ blocked = true;
265
+ system.facts.injectionAttempts =
266
+ (system.facts.injectionAttempts as number) + 1;
267
+ for (const p of injectionResult.patterns) {
268
+ addTimeline("injection", `${p.name} (${p.severity})`, "injection");
269
+ }
270
+ }
271
+
272
+ emitDevToolsEvent({
273
+ type: "guardrail_check",
274
+ guardrailName: "prompt-injection",
275
+ guardrailType: "input",
276
+ passed: !injectionResult.detected,
277
+ inputLength: text.length,
278
+ });
279
+
280
+ // 2. PII detection. detectPII is detection-only; detectAndRedactPII also
281
+ // populates `redactedText` so we never have to mutate a returned result.
282
+ const redactionEnabled = system.facts.redactionEnabled;
283
+ const piiResult = redactionEnabled
284
+ ? await detectAndRedactPII(text, { style: "typed" })
285
+ : await detectPII(text);
286
+ if (piiResult.detected) {
287
+ system.facts.piiDetections = (system.facts.piiDetections as number) + 1;
288
+ for (const item of piiResult.items) {
289
+ addTimeline("pii", `${item.type} found`, "pii");
290
+ }
291
+ }
292
+
293
+ emitDevToolsEvent({
294
+ type: "guardrail_check",
295
+ guardrailName: "pii-detection",
296
+ guardrailType: "input",
297
+ passed: !piiResult.detected,
298
+ inputLength: text.length,
299
+ });
300
+
301
+ // 3. Compliance check
302
+ if (runComplianceCheck(piiResult, text.length)) {
303
+ blocked = true;
304
+ }
305
+
306
+ if (blocked) {
307
+ system.facts.blockedCount = (system.facts.blockedCount as number) + 1;
308
+ }
309
+
310
+ if (!blocked && !piiResult.detected) {
311
+ addTimeline("pass", "message passed all checks", "pass");
312
+ }
313
+
314
+ const redactedText = piiResult.redactedText ?? text;
315
+
316
+ return {
317
+ id,
318
+ text: redactionEnabled ? redactedText : text,
319
+ blocked,
320
+ redactedText,
321
+ injectionResult: injectionResult.detected ? injectionResult : null,
322
+ piiResult: toSafePiiResult(piiResult, redactionEnabled),
323
+ };
324
+ }
325
+ ```
326
+
327
+ ## topic-guard
328
+
329
+ ```typescript
330
+ // Example: topic-guard
331
+ // Source: examples/topic-guard/src/topic-guard.ts
332
+ // Pure module file — no DOM wiring
333
+
334
+ /**
335
+ * Topic Guard — Directive Module
336
+ *
337
+ * Demonstrates input guardrails for AI agents. Messages are checked against
338
+ * configurable guardrails before reaching the mock agent. Blocked messages
339
+ * are rejected with an explanation; allowed messages get a mock response.
340
+ */
341
+
342
+ import { type ModuleSchema, createModule, t } from "@directive-run/core";
343
+ import {
344
+ type GuardrailResult,
345
+ checkKeywordGuardrail,
346
+ checkTopicClassifier,
347
+ getMockAgentResponse,
348
+ } from "./mock-guardrails.js";
349
+
350
+ // ============================================================================
351
+ // Types
352
+ // ============================================================================
353
+
354
+ export interface ChatMessage {
355
+ role: "user" | "agent" | "system";
356
+ text: string;
357
+ blocked: boolean;
358
+ guardrail?: string;
359
+ }
360
+
361
+ export interface GuardrailLogEntry {
362
+ timestamp: number;
363
+ input: string;
364
+ result: GuardrailResult;
365
+ }
366
+
367
+ // ============================================================================
368
+ // Schema
369
+ // ============================================================================
370
+
371
+ export const topicGuardSchema = {
372
+ facts: {
373
+ input: t.string(),
374
+ messages: t.array<ChatMessage>(),
375
+ isProcessing: t.boolean(),
376
+ lastGuardrailResult: t.object<GuardrailResult | null>(),
377
+ guardrailLog: t.array<GuardrailLogEntry>(),
378
+ allowedTopics: t.array<string>(),
379
+ },
380
+ derivations: {
381
+ messageCount: t.number(),
382
+ blockedCount: t.number(),
383
+ allowedCount: t.number(),
384
+ blockRate: t.string(),
385
+ canSend: t.boolean(),
386
+ lastMessageBlocked: t.boolean(),
387
+ },
388
+ events: {
389
+ send: {},
390
+ clear: {},
391
+ setInput: { value: t.string() },
392
+ toggleTopic: { topic: t.string() },
393
+ },
394
+ requirements: {
395
+ BLOCK_MESSAGE: {
396
+ reason: t.string(),
397
+ guardrailName: t.string(),
398
+ },
399
+ ALLOW_MESSAGE: {},
400
+ },
401
+ } satisfies ModuleSchema;
402
+
403
+ // ============================================================================
404
+ // Module
405
+ // ============================================================================
406
+
407
+ export const topicGuardModule = createModule("topic-guard", {
408
+ schema: topicGuardSchema,
409
+
410
+ init: (facts) => {
411
+ facts.input = "";
412
+ facts.messages = [];
413
+ facts.isProcessing = false;
414
+ facts.lastGuardrailResult = null;
415
+ facts.guardrailLog = [];
416
+ facts.allowedTopics = ["product", "billing", "support", "technical"];
417
+ },
418
+
419
+ // ============================================================================
420
+ // Derivations
421
+ // ============================================================================
422
+
423
+ derive: {
424
+ messageCount: (facts) => {
425
+ return facts.messages.filter((m) => m.role === "user").length;
426
+ },
427
+
428
+ blockedCount: (facts) => {
429
+ return facts.messages.filter((m) => m.role === "user" && m.blocked)
430
+ .length;
431
+ },
432
+
433
+ allowedCount: (facts) => {
434
+ return facts.messages.filter((m) => m.role === "user" && !m.blocked)
435
+ .length;
436
+ },
437
+
438
+ blockRate: (_facts, derived) => {
439
+ const total = derived.messageCount;
440
+ if (total === 0) {
441
+ return "0%";
442
+ }
443
+ const blocked = derived.blockedCount;
444
+ const rate = Math.round((blocked / total) * 100);
445
+
446
+ return `${rate}%`;
447
+ },
448
+
449
+ canSend: (facts) => {
450
+ return facts.input.trim().length > 0 && !facts.isProcessing;
451
+ },
452
+
453
+ lastMessageBlocked: (facts) => {
454
+ const msgs = facts.messages;
455
+ if (msgs.length === 0) {
456
+ return false;
457
+ }
458
+
459
+ return msgs[msgs.length - 1].blocked;
460
+ },
461
+ },
462
+
463
+ // ============================================================================
464
+ // Events
465
+ // ============================================================================
466
+
467
+ events: {
468
+ send: (facts) => {
469
+ const text = facts.input.trim();
470
+ if (text.length === 0 || facts.isProcessing) {
471
+ return;
472
+ }
473
+
474
+ // Add user message
475
+ const messages = [...facts.messages];
476
+ messages.push({ role: "user", text, blocked: false });
477
+ facts.messages = messages;
478
+
479
+ // Run guardrails
480
+ const keywordResult = checkKeywordGuardrail(text);
481
+ if (keywordResult.blocked) {
482
+ facts.lastGuardrailResult = keywordResult;
483
+ facts.isProcessing = true;
484
+ facts.input = "";
485
+
486
+ return;
487
+ }
488
+
489
+ const classifierResult = checkTopicClassifier(text, facts.allowedTopics);
490
+ facts.lastGuardrailResult = classifierResult;
491
+ facts.isProcessing = true;
492
+ facts.input = "";
493
+ },
494
+
495
+ clear: (facts) => {
496
+ facts.messages = [];
497
+ facts.guardrailLog = [];
498
+ facts.lastGuardrailResult = null;
499
+ facts.isProcessing = false;
500
+ },
501
+
502
+ setInput: (facts, { value }) => {
503
+ facts.input = value;
504
+ },
505
+
506
+ toggleTopic: (facts, { topic }) => {
507
+ const topics = [...facts.allowedTopics];
508
+ const idx = topics.indexOf(topic);
509
+ if (idx >= 0) {
510
+ topics.splice(idx, 1);
511
+ } else {
512
+ topics.push(topic);
513
+ }
514
+ facts.allowedTopics = topics;
515
+ },
516
+ },
517
+
518
+ // ============================================================================
519
+ // Constraints
520
+ // ============================================================================
521
+
522
+ constraints: {
523
+ offTopicDetected: {
524
+ priority: 100,
525
+ when: (facts) => {
526
+ return (
527
+ facts.lastGuardrailResult?.blocked === true && facts.isProcessing
528
+ );
529
+ },
530
+ require: (facts) => {
531
+ const result = facts.lastGuardrailResult!;
532
+
533
+ return {
534
+ type: "BLOCK_MESSAGE",
535
+ reason: result.reason,
536
+ guardrailName: result.guardrailName,
537
+ };
538
+ },
539
+ },
540
+
541
+ onTopicConfirmed: {
542
+ priority: 90,
543
+ when: (facts) => {
544
+ return (
545
+ facts.lastGuardrailResult?.blocked === false && facts.isProcessing
546
+ );
547
+ },
548
+ require: () => ({
549
+ type: "ALLOW_MESSAGE",
550
+ }),
551
+ },
552
+ },
553
+
554
+ // ============================================================================
555
+ // Resolvers
556
+ // ============================================================================
557
+
558
+ resolvers: {
559
+ blockMessage: {
560
+ requirement: "BLOCK_MESSAGE",
561
+ resolve: async (req, context) => {
562
+ const messages = [...context.facts.messages];
563
+ // Mark the last user message as blocked
564
+ const lastUserIdx = messages.length - 1;
565
+ if (lastUserIdx >= 0) {
566
+ messages[lastUserIdx] = {
567
+ ...messages[lastUserIdx],
568
+ blocked: true,
569
+ guardrail: req.guardrailName,
570
+ };
571
+ }
572
+ // Add system rejection message
573
+ messages.push({
574
+ role: "system",
575
+ text: "I can only help with product-related questions.",
576
+ blocked: true,
577
+ guardrail: req.guardrailName,
578
+ });
579
+ context.facts.messages = messages;
580
+ context.facts.isProcessing = false;
581
+ },
582
+ },
583
+
584
+ allowMessage: {
585
+ requirement: "ALLOW_MESSAGE",
586
+ resolve: async (_req, context) => {
587
+ const messages = [...context.facts.messages];
588
+ const lastUserMsg = messages.filter((m) => m.role === "user").pop();
589
+ const responseText = getMockAgentResponse(lastUserMsg?.text ?? "");
590
+ messages.push({
591
+ role: "agent",
592
+ text: responseText,
593
+ blocked: false,
594
+ });
595
+ context.facts.messages = messages;
596
+ context.facts.isProcessing = false;
597
+ },
598
+ },
599
+ },
600
+
601
+ // ============================================================================
602
+ // Effects
603
+ // ============================================================================
604
+
605
+ effects: {
606
+ logGuardrailResult: {
607
+ deps: ["lastGuardrailResult"],
608
+ run: (facts) => {
609
+ const result = facts.lastGuardrailResult;
610
+ if (!result) {
611
+ return;
612
+ }
613
+
614
+ const lastUserMsg = [...facts.messages]
615
+ .reverse()
616
+ .find((m) => m.role === "user");
617
+ const log = [...facts.guardrailLog];
618
+ log.push({
619
+ timestamp: Date.now(),
620
+ input: lastUserMsg?.text ?? "",
621
+ result,
622
+ });
623
+ facts.guardrailLog = log;
624
+ },
625
+ },
626
+ },
627
+ });
628
+ ```