@vellumai/assistant 0.3.26 → 0.3.28

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 (82) hide show
  1. package/ARCHITECTURE.md +48 -1
  2. package/Dockerfile +2 -2
  3. package/package.json +1 -1
  4. package/scripts/ipc/generate-swift.ts +6 -2
  5. package/src/__tests__/agent-loop.test.ts +119 -0
  6. package/src/__tests__/bundled-asset.test.ts +107 -0
  7. package/src/__tests__/canonical-guardian-store.test.ts +636 -0
  8. package/src/__tests__/channel-approval-routes.test.ts +174 -1
  9. package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
  10. package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
  11. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
  12. package/src/__tests__/guardian-dispatch.test.ts +19 -19
  13. package/src/__tests__/guardian-routing-invariants.test.ts +954 -0
  14. package/src/__tests__/mcp-cli.test.ts +77 -0
  15. package/src/__tests__/non-member-access-request.test.ts +31 -29
  16. package/src/__tests__/notification-decision-fallback.test.ts +61 -3
  17. package/src/__tests__/notification-decision-strategy.test.ts +17 -0
  18. package/src/__tests__/notification-guardian-path.test.ts +13 -15
  19. package/src/__tests__/onboarding-template-contract.test.ts +116 -21
  20. package/src/__tests__/secret-scanner-executor.test.ts +59 -0
  21. package/src/__tests__/secret-scanner.test.ts +8 -0
  22. package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
  23. package/src/__tests__/session-runtime-assembly.test.ts +76 -47
  24. package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
  25. package/src/agent/loop.ts +46 -3
  26. package/src/approvals/guardian-decision-primitive.ts +285 -0
  27. package/src/approvals/guardian-request-resolvers.ts +539 -0
  28. package/src/calls/guardian-dispatch.ts +46 -40
  29. package/src/calls/relay-server.ts +147 -2
  30. package/src/calls/types.ts +1 -1
  31. package/src/config/system-prompt.ts +2 -1
  32. package/src/config/templates/BOOTSTRAP.md +47 -31
  33. package/src/config/templates/USER.md +5 -0
  34. package/src/config/update-bulletin-template-path.ts +4 -1
  35. package/src/config/vellum-skills/trusted-contacts/SKILL.md +22 -17
  36. package/src/daemon/handlers/guardian-actions.ts +45 -66
  37. package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
  38. package/src/daemon/lifecycle.ts +3 -16
  39. package/src/daemon/server.ts +18 -0
  40. package/src/daemon/session-agent-loop-handlers.ts +5 -4
  41. package/src/daemon/session-agent-loop.ts +32 -5
  42. package/src/daemon/session-process.ts +68 -307
  43. package/src/daemon/session-runtime-assembly.ts +112 -24
  44. package/src/daemon/session-tool-setup.ts +1 -0
  45. package/src/daemon/session.ts +1 -0
  46. package/src/home-base/prebuilt/seed.ts +2 -1
  47. package/src/hooks/templates.ts +2 -1
  48. package/src/memory/canonical-guardian-store.ts +524 -0
  49. package/src/memory/channel-guardian-store.ts +1 -0
  50. package/src/memory/db-init.ts +16 -0
  51. package/src/memory/guardian-action-store.ts +7 -60
  52. package/src/memory/guardian-approvals.ts +9 -4
  53. package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
  54. package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
  55. package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
  56. package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
  57. package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
  58. package/src/memory/migrations/index.ts +4 -0
  59. package/src/memory/migrations/registry.ts +5 -0
  60. package/src/memory/schema-migration.ts +1 -0
  61. package/src/memory/schema.ts +52 -0
  62. package/src/notifications/copy-composer.ts +16 -4
  63. package/src/notifications/decision-engine.ts +57 -0
  64. package/src/permissions/defaults.ts +2 -0
  65. package/src/runtime/access-request-helper.ts +137 -0
  66. package/src/runtime/actor-trust-resolver.ts +225 -0
  67. package/src/runtime/channel-guardian-service.ts +12 -4
  68. package/src/runtime/guardian-context-resolver.ts +32 -7
  69. package/src/runtime/guardian-decision-types.ts +6 -0
  70. package/src/runtime/guardian-reply-router.ts +687 -0
  71. package/src/runtime/http-server.ts +8 -0
  72. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
  73. package/src/runtime/routes/conversation-routes.ts +18 -0
  74. package/src/runtime/routes/guardian-action-routes.ts +100 -109
  75. package/src/runtime/routes/inbound-message-handler.ts +170 -525
  76. package/src/runtime/tool-grant-request-helper.ts +195 -0
  77. package/src/tools/executor.ts +13 -1
  78. package/src/tools/sensitive-output-placeholders.ts +203 -0
  79. package/src/tools/tool-approval-handler.ts +44 -1
  80. package/src/tools/types.ts +11 -0
  81. package/src/util/bundled-asset.ts +31 -0
  82. package/src/util/canonicalize-identity.ts +52 -0
@@ -0,0 +1,208 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import {
4
+ applyStreamingSubstitution,
5
+ applySubstitutions,
6
+ extractAndSanitize,
7
+ } from '../tools/sensitive-output-placeholders.js';
8
+
9
+ describe('extractAndSanitize', () => {
10
+ test('parses a valid invite_code directive and replaces raw value with placeholder', () => {
11
+ const rawToken = 'abc123def456';
12
+ const content = `<vellum-sensitive-output kind="invite_code" value="${rawToken}" />\nhttps://t.me/bot?start=iv_${rawToken}`;
13
+
14
+ const { sanitizedContent, bindings } = extractAndSanitize(content);
15
+
16
+ expect(bindings).toHaveLength(1);
17
+ expect(bindings[0].kind).toBe('invite_code');
18
+ expect(bindings[0].value).toBe(rawToken);
19
+ expect(bindings[0].placeholder).toMatch(/^VELLUM_ASSISTANT_INVITE_CODE_[A-Z0-9]{8}$/);
20
+
21
+ // Directive tag should be stripped
22
+ expect(sanitizedContent).not.toContain('<vellum-sensitive-output');
23
+ // Raw token should be replaced with placeholder
24
+ expect(sanitizedContent).not.toContain(rawToken);
25
+ expect(sanitizedContent).toContain(bindings[0].placeholder);
26
+ // The link structure should be preserved
27
+ expect(sanitizedContent).toContain(`https://t.me/bot?start=iv_${bindings[0].placeholder}`);
28
+ });
29
+
30
+ test('ignores malformed directives safely', () => {
31
+ const content = 'Some text <vellum-sensitive-output broken />';
32
+ const { sanitizedContent, bindings } = extractAndSanitize(content);
33
+
34
+ expect(bindings).toHaveLength(0);
35
+ expect(sanitizedContent).toBe(content);
36
+ });
37
+
38
+ test('ignores unknown kind values', () => {
39
+ const content = '<vellum-sensitive-output kind="unknown_kind" value="secret123" />';
40
+ const { sanitizedContent, bindings } = extractAndSanitize(content);
41
+
42
+ expect(bindings).toHaveLength(0);
43
+ expect(sanitizedContent).toBe(content);
44
+ });
45
+
46
+ test('drops empty values', () => {
47
+ const content = '<vellum-sensitive-output kind="invite_code" value="" />';
48
+ const { sanitizedContent, bindings } = extractAndSanitize(content);
49
+
50
+ expect(bindings).toHaveLength(0);
51
+ // Directive should remain since no valid binding was extracted
52
+ expect(sanitizedContent).toBe(content);
53
+ });
54
+
55
+ test('deduplicates identical values into a single binding', () => {
56
+ const rawToken = 'token123';
57
+ const content = [
58
+ `<vellum-sensitive-output kind="invite_code" value="${rawToken}" />`,
59
+ `<vellum-sensitive-output kind="invite_code" value="${rawToken}" />`,
60
+ `Link1: https://t.me/bot?start=iv_${rawToken}`,
61
+ `Link2: https://t.me/bot?start=iv_${rawToken}`,
62
+ ].join('\n');
63
+
64
+ const { sanitizedContent, bindings } = extractAndSanitize(content);
65
+
66
+ expect(bindings).toHaveLength(1);
67
+ expect(bindings[0].value).toBe(rawToken);
68
+
69
+ // Both occurrences of the raw token should be replaced with the same placeholder
70
+ const placeholderCount = sanitizedContent.split(bindings[0].placeholder).length - 1;
71
+ expect(placeholderCount).toBe(2);
72
+ expect(sanitizedContent).not.toContain(rawToken);
73
+ });
74
+
75
+ test('supports multiple distinct bindings', () => {
76
+ const token1 = 'firstToken123';
77
+ const token2 = 'secondToken456';
78
+ const content = [
79
+ `<vellum-sensitive-output kind="invite_code" value="${token1}" />`,
80
+ `<vellum-sensitive-output kind="invite_code" value="${token2}" />`,
81
+ `Link1: https://t.me/bot?start=iv_${token1}`,
82
+ `Link2: https://t.me/bot?start=iv_${token2}`,
83
+ ].join('\n');
84
+
85
+ const { sanitizedContent, bindings } = extractAndSanitize(content);
86
+
87
+ expect(bindings).toHaveLength(2);
88
+ expect(bindings[0].value).toBe(token1);
89
+ expect(bindings[1].value).toBe(token2);
90
+
91
+ // Both raw tokens should be replaced
92
+ expect(sanitizedContent).not.toContain(token1);
93
+ expect(sanitizedContent).not.toContain(token2);
94
+ expect(sanitizedContent).toContain(bindings[0].placeholder);
95
+ expect(sanitizedContent).toContain(bindings[1].placeholder);
96
+ });
97
+
98
+ test('returns content unchanged when no directives are present', () => {
99
+ const content = 'Just a normal tool output with no directives.';
100
+ const { sanitizedContent, bindings } = extractAndSanitize(content);
101
+
102
+ expect(bindings).toHaveLength(0);
103
+ expect(sanitizedContent).toBe(content);
104
+ });
105
+
106
+ test('placeholder format matches required pattern', () => {
107
+ const content = '<vellum-sensitive-output kind="invite_code" value="tok123" />';
108
+ const { bindings } = extractAndSanitize(content);
109
+
110
+ expect(bindings).toHaveLength(1);
111
+ // Must be exactly: VELLUM_ASSISTANT_INVITE_CODE_ followed by 8 uppercase alphanumeric chars
112
+ expect(bindings[0].placeholder).toMatch(/^VELLUM_ASSISTANT_INVITE_CODE_[A-Z0-9]{8}$/);
113
+ });
114
+ });
115
+
116
+ describe('applySubstitutions', () => {
117
+ test('replaces placeholders with real values', () => {
118
+ const map = new Map([
119
+ ['VELLUM_ASSISTANT_INVITE_CODE_ABCD1234', 'realtoken123'],
120
+ ]);
121
+
122
+ const text = 'Your link: https://t.me/bot?start=iv_VELLUM_ASSISTANT_INVITE_CODE_ABCD1234';
123
+ const result = applySubstitutions(text, map);
124
+
125
+ expect(result).toBe('Your link: https://t.me/bot?start=iv_realtoken123');
126
+ });
127
+
128
+ test('replaces multiple placeholders', () => {
129
+ const map = new Map([
130
+ ['VELLUM_ASSISTANT_INVITE_CODE_AAAA1111', 'token1'],
131
+ ['VELLUM_ASSISTANT_INVITE_CODE_BBBB2222', 'token2'],
132
+ ]);
133
+
134
+ const text = 'Link1: VELLUM_ASSISTANT_INVITE_CODE_AAAA1111, Link2: VELLUM_ASSISTANT_INVITE_CODE_BBBB2222';
135
+ const result = applySubstitutions(text, map);
136
+
137
+ expect(result).toBe('Link1: token1, Link2: token2');
138
+ });
139
+
140
+ test('returns text unchanged when map is empty', () => {
141
+ const map = new Map<string, string>();
142
+ const text = 'No placeholders here.';
143
+ expect(applySubstitutions(text, map)).toBe(text);
144
+ });
145
+ });
146
+
147
+ describe('applyStreamingSubstitution', () => {
148
+ test('resolves complete placeholders in a single chunk', () => {
149
+ const map = new Map([
150
+ ['VELLUM_ASSISTANT_INVITE_CODE_ABCD1234', 'realtoken'],
151
+ ]);
152
+
153
+ const { emit, pending } = applyStreamingSubstitution(
154
+ 'Your code: VELLUM_ASSISTANT_INVITE_CODE_ABCD1234 is ready.',
155
+ map,
156
+ );
157
+
158
+ expect(emit).toContain('realtoken');
159
+ expect(emit).not.toContain('VELLUM_ASSISTANT_INVITE_CODE_ABCD1234');
160
+ // No pending text since the placeholder was complete
161
+ expect(pending).toBe('');
162
+ });
163
+
164
+ test('buffers text that could be an incomplete placeholder prefix', () => {
165
+ const map = new Map([
166
+ ['VELLUM_ASSISTANT_INVITE_CODE_ABCD1234', 'realtoken'],
167
+ ]);
168
+
169
+ // Chunk ends mid-placeholder
170
+ const { emit, pending } = applyStreamingSubstitution(
171
+ 'Your code: VELLUM_ASSISTANT_',
172
+ map,
173
+ );
174
+
175
+ // The ambiguous tail should be buffered
176
+ expect(pending.length).toBeGreaterThan(0);
177
+ // emit should not contain the partial placeholder
178
+ expect(emit).not.toContain('VELLUM_ASSISTANT_');
179
+ });
180
+
181
+ test('handles split-chunk placeholder by concatenating pending with next chunk', () => {
182
+ const map = new Map([
183
+ ['VELLUM_ASSISTANT_INVITE_CODE_ABCD1234', 'realtoken'],
184
+ ]);
185
+
186
+ // First chunk: partial placeholder
187
+ const result1 = applyStreamingSubstitution(
188
+ 'Link: VELLUM_ASSISTANT_',
189
+ map,
190
+ );
191
+
192
+ // Second chunk completes the placeholder
193
+ const combined = result1.pending + 'INVITE_CODE_ABCD1234 done.';
194
+ const result2 = applyStreamingSubstitution(combined, map);
195
+
196
+ expect(result2.emit).toContain('realtoken');
197
+ expect(result2.emit).toContain('done.');
198
+ expect(result2.pending).toBe('');
199
+ });
200
+
201
+ test('returns text unchanged when map is empty', () => {
202
+ const map = new Map<string, string>();
203
+ const { emit, pending } = applyStreamingSubstitution('Hello world', map);
204
+
205
+ expect(emit).toBe('Hello world');
206
+ expect(pending).toBe('');
207
+ });
208
+ });
@@ -1,19 +1,19 @@
1
1
  import { describe, expect,test } from 'bun:test';
2
2
 
3
3
  import { buildChannelAwarenessSection } from '../config/system-prompt.js';
4
- import type { ChannelCapabilities, ChannelTurnContextParams, GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
4
+ import type { ChannelCapabilities, ChannelTurnContextParams, InboundActorContext } from '../daemon/session-runtime-assembly.js';
5
5
  import {
6
6
  applyRuntimeInjections,
7
7
  buildChannelTurnContextBlock,
8
8
  injectChannelCapabilityContext,
9
9
  injectChannelTurnContext,
10
- injectGuardianContext,
10
+ injectInboundActorContext,
11
11
  injectTemporalContext,
12
12
  resolveChannelCapabilities,
13
13
  sanitizePttActivationKey,
14
14
  stripChannelCapabilityContext,
15
15
  stripChannelTurnContext,
16
- stripGuardianContext,
16
+ stripInboundActorContext,
17
17
  stripTemporalContext,
18
18
  } from '../daemon/session-runtime-assembly.js';
19
19
  import type { Message } from '../providers/types.js';
@@ -534,90 +534,118 @@ describe('applyRuntimeInjections with temporalContext', () => {
534
534
  });
535
535
 
536
536
  // ---------------------------------------------------------------------------
537
- // guardian_context
537
+ // inbound_actor_context
538
538
  // ---------------------------------------------------------------------------
539
539
 
540
- describe('injectGuardianContext', () => {
540
+ describe('injectInboundActorContext', () => {
541
541
  const baseUserMessage: Message = {
542
542
  role: 'user',
543
543
  content: [{ type: 'text', text: 'Can you text me updates?' }],
544
544
  };
545
545
 
546
- test('prepends guardian_context block to user message', () => {
547
- const ctx: GuardianRuntimeContext = {
546
+ test('prepends inbound_actor_context block to user message', () => {
547
+ const ctx: InboundActorContext = {
548
548
  sourceChannel: 'sms',
549
- actorRole: 'guardian',
550
- guardianExternalUserId: 'guardian-user-1',
551
- guardianChatId: '+15550001111',
552
- requesterIdentifier: '+15550001111',
553
- requesterExternalUserId: 'guardian-user-1',
554
- requesterChatId: '+15550001111',
549
+ canonicalActorIdentity: 'guardian-user-1',
550
+ actorIdentifier: '+15550001111',
551
+ trustClass: 'guardian',
552
+ guardianIdentity: 'guardian-user-1',
555
553
  };
556
554
 
557
- const result = injectGuardianContext(baseUserMessage, ctx);
555
+ const result = injectInboundActorContext(baseUserMessage, ctx);
558
556
  expect(result.content.length).toBe(2);
559
557
  const injected = result.content[0];
560
558
  expect(injected.type).toBe('text');
561
559
  const text = (injected as { type: 'text'; text: string }).text;
562
- expect(text).toContain('<guardian_context>');
563
- expect(text).toContain('actor_role: guardian');
560
+ expect(text).toContain('<inbound_actor_context>');
561
+ expect(text).toContain('trust_class: guardian');
564
562
  expect(text).toContain('source_channel: sms');
565
- expect(text).toContain('</guardian_context>');
563
+ expect(text).toContain('canonical_actor_identity: guardian-user-1');
564
+ expect(text).toContain('</inbound_actor_context>');
566
565
  });
567
566
 
568
- test('includes behavioral guidance for non-guardian actors', () => {
569
- const ctx: GuardianRuntimeContext = {
567
+ test('includes behavioral guidance for trusted_contact actors', () => {
568
+ const ctx: InboundActorContext = {
570
569
  sourceChannel: 'telegram',
571
- actorRole: 'non-guardian',
572
- guardianExternalUserId: 'guardian-user-1',
573
- guardianChatId: 'chat-1',
574
- requesterIdentifier: '@someone',
575
- requesterExternalUserId: 'other-user-1',
576
- requesterChatId: 'chat-2',
570
+ canonicalActorIdentity: 'other-user-1',
571
+ actorIdentifier: '@someone',
572
+ trustClass: 'trusted_contact',
573
+ guardianIdentity: 'guardian-user-1',
574
+ memberStatus: 'active',
575
+ memberPolicy: 'default',
577
576
  };
578
577
 
579
- const result = injectGuardianContext(baseUserMessage, ctx);
578
+ const result = injectInboundActorContext(baseUserMessage, ctx);
580
579
  const text = (result.content[0] as { type: 'text'; text: string }).text;
581
580
  expect(text).toContain('non-guardian account');
582
581
  expect(text).toContain('Do not explain the verification system');
582
+ expect(text).toContain('member_status: active');
583
+ expect(text).toContain('member_policy: default');
584
+ });
585
+
586
+ test('includes behavioral guidance for unknown actors', () => {
587
+ const ctx: InboundActorContext = {
588
+ sourceChannel: 'telegram',
589
+ canonicalActorIdentity: null,
590
+ trustClass: 'unknown',
591
+ denialReason: 'no_identity',
592
+ };
593
+
594
+ const result = injectInboundActorContext(baseUserMessage, ctx);
595
+ const text = (result.content[0] as { type: 'text'; text: string }).text;
596
+ expect(text).toContain('non-guardian account');
597
+ expect(text).toContain('Do not explain the verification system');
598
+ expect(text).toContain('denial_reason: no_identity');
583
599
  });
584
600
 
585
601
  test('omits non-guardian behavioral guidance for guardian actors', () => {
586
- const ctx: GuardianRuntimeContext = {
602
+ const ctx: InboundActorContext = {
587
603
  sourceChannel: 'telegram',
588
- actorRole: 'guardian',
589
- guardianExternalUserId: 'guardian-user-1',
590
- guardianChatId: 'chat-1',
591
- requesterIdentifier: '@guardian',
592
- requesterExternalUserId: 'guardian-user-1',
593
- requesterChatId: 'chat-1',
604
+ canonicalActorIdentity: 'guardian-user-1',
605
+ actorIdentifier: '@guardian',
606
+ trustClass: 'guardian',
607
+ guardianIdentity: 'guardian-user-1',
594
608
  };
595
609
 
596
- const result = injectGuardianContext(baseUserMessage, ctx);
610
+ const result = injectInboundActorContext(baseUserMessage, ctx);
597
611
  const text = (result.content[0] as { type: 'text'; text: string }).text;
598
612
  expect(text).not.toContain('non-guardian account');
599
613
  });
614
+
615
+ test('omits member_status and member_policy when not provided', () => {
616
+ const ctx: InboundActorContext = {
617
+ sourceChannel: 'sms',
618
+ canonicalActorIdentity: 'user-1',
619
+ trustClass: 'unknown',
620
+ denialReason: 'no_binding',
621
+ };
622
+
623
+ const result = injectInboundActorContext(baseUserMessage, ctx);
624
+ const text = (result.content[0] as { type: 'text'; text: string }).text;
625
+ expect(text).not.toContain('member_status');
626
+ expect(text).not.toContain('member_policy');
627
+ });
600
628
  });
601
629
 
602
- describe('stripGuardianContext', () => {
603
- test('strips guardian_context blocks from user messages', () => {
630
+ describe('stripInboundActorContext', () => {
631
+ test('strips inbound_actor_context blocks from user messages', () => {
604
632
  const messages: Message[] = [
605
633
  {
606
634
  role: 'user',
607
635
  content: [
608
- { type: 'text', text: '<guardian_context>\nactor_role: guardian\n</guardian_context>' },
636
+ { type: 'text', text: '<inbound_actor_context>\ntrust_class: guardian\n</inbound_actor_context>' },
609
637
  { type: 'text', text: 'Hello' },
610
638
  ],
611
639
  },
612
640
  ];
613
- const result = stripGuardianContext(messages);
641
+ const result = stripInboundActorContext(messages);
614
642
  expect(result).toHaveLength(1);
615
643
  expect(result[0].content).toHaveLength(1);
616
644
  expect((result[0].content[0] as { type: 'text'; text: string }).text).toBe('Hello');
617
645
  });
618
646
  });
619
647
 
620
- describe('applyRuntimeInjections with guardianContext', () => {
648
+ describe('applyRuntimeInjections with inboundActorContext', () => {
621
649
  const baseMessages: Message[] = [
622
650
  {
623
651
  role: 'user',
@@ -625,20 +653,21 @@ describe('applyRuntimeInjections with guardianContext', () => {
625
653
  },
626
654
  ];
627
655
 
628
- test('injects guardian context when provided', () => {
656
+ test('injects inbound actor context when provided', () => {
629
657
  const result = applyRuntimeInjections(baseMessages, {
630
- guardianContext: {
658
+ inboundActorContext: {
631
659
  sourceChannel: 'sms',
632
- actorRole: 'non-guardian',
633
- guardianExternalUserId: 'guardian-1',
634
- requesterExternalUserId: 'requester-1',
635
- requesterIdentifier: '+15550002222',
636
- requesterChatId: '+15550002222',
660
+ canonicalActorIdentity: 'requester-1',
661
+ actorIdentifier: '+15550002222',
662
+ trustClass: 'trusted_contact',
663
+ guardianIdentity: 'guardian-1',
664
+ memberStatus: 'active',
665
+ memberPolicy: 'default',
637
666
  },
638
667
  });
639
668
  expect(result).toHaveLength(1);
640
669
  expect(result[0].content).toHaveLength(2);
641
- expect((result[0].content[0] as { type: 'text'; text: string }).text).toContain('<guardian_context>');
670
+ expect((result[0].content[0] as { type: 'text'; text: string }).text).toContain('<inbound_actor_context>');
642
671
  });
643
672
  });
644
673