@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.
- package/ARCHITECTURE.md +48 -1
- package/Dockerfile +2 -2
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +6 -2
- package/src/__tests__/agent-loop.test.ts +119 -0
- package/src/__tests__/bundled-asset.test.ts +107 -0
- package/src/__tests__/canonical-guardian-store.test.ts +636 -0
- package/src/__tests__/channel-approval-routes.test.ts +174 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
- package/src/__tests__/guardian-dispatch.test.ts +19 -19
- package/src/__tests__/guardian-routing-invariants.test.ts +954 -0
- package/src/__tests__/mcp-cli.test.ts +77 -0
- package/src/__tests__/non-member-access-request.test.ts +31 -29
- package/src/__tests__/notification-decision-fallback.test.ts +61 -3
- package/src/__tests__/notification-decision-strategy.test.ts +17 -0
- package/src/__tests__/notification-guardian-path.test.ts +13 -15
- package/src/__tests__/onboarding-template-contract.test.ts +116 -21
- package/src/__tests__/secret-scanner-executor.test.ts +59 -0
- package/src/__tests__/secret-scanner.test.ts +8 -0
- package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
- package/src/__tests__/session-runtime-assembly.test.ts +76 -47
- package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
- package/src/agent/loop.ts +46 -3
- package/src/approvals/guardian-decision-primitive.ts +285 -0
- package/src/approvals/guardian-request-resolvers.ts +539 -0
- package/src/calls/guardian-dispatch.ts +46 -40
- package/src/calls/relay-server.ts +147 -2
- package/src/calls/types.ts +1 -1
- package/src/config/system-prompt.ts +2 -1
- package/src/config/templates/BOOTSTRAP.md +47 -31
- package/src/config/templates/USER.md +5 -0
- package/src/config/update-bulletin-template-path.ts +4 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +22 -17
- package/src/daemon/handlers/guardian-actions.ts +45 -66
- package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
- package/src/daemon/lifecycle.ts +3 -16
- package/src/daemon/server.ts +18 -0
- package/src/daemon/session-agent-loop-handlers.ts +5 -4
- package/src/daemon/session-agent-loop.ts +32 -5
- package/src/daemon/session-process.ts +68 -307
- package/src/daemon/session-runtime-assembly.ts +112 -24
- package/src/daemon/session-tool-setup.ts +1 -0
- package/src/daemon/session.ts +1 -0
- package/src/home-base/prebuilt/seed.ts +2 -1
- package/src/hooks/templates.ts +2 -1
- package/src/memory/canonical-guardian-store.ts +524 -0
- package/src/memory/channel-guardian-store.ts +1 -0
- package/src/memory/db-init.ts +16 -0
- package/src/memory/guardian-action-store.ts +7 -60
- package/src/memory/guardian-approvals.ts +9 -4
- package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
- package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
- package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +5 -0
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +52 -0
- package/src/notifications/copy-composer.ts +16 -4
- package/src/notifications/decision-engine.ts +57 -0
- package/src/permissions/defaults.ts +2 -0
- package/src/runtime/access-request-helper.ts +137 -0
- package/src/runtime/actor-trust-resolver.ts +225 -0
- package/src/runtime/channel-guardian-service.ts +12 -4
- package/src/runtime/guardian-context-resolver.ts +32 -7
- package/src/runtime/guardian-decision-types.ts +6 -0
- package/src/runtime/guardian-reply-router.ts +687 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
- package/src/runtime/routes/conversation-routes.ts +18 -0
- package/src/runtime/routes/guardian-action-routes.ts +100 -109
- package/src/runtime/routes/inbound-message-handler.ts +170 -525
- package/src/runtime/tool-grant-request-helper.ts +195 -0
- package/src/tools/executor.ts +13 -1
- package/src/tools/sensitive-output-placeholders.ts +203 -0
- package/src/tools/tool-approval-handler.ts +44 -1
- package/src/tools/types.ts +11 -0
- package/src/util/bundled-asset.ts +31 -0
- 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,
|
|
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
|
-
|
|
10
|
+
injectInboundActorContext,
|
|
11
11
|
injectTemporalContext,
|
|
12
12
|
resolveChannelCapabilities,
|
|
13
13
|
sanitizePttActivationKey,
|
|
14
14
|
stripChannelCapabilityContext,
|
|
15
15
|
stripChannelTurnContext,
|
|
16
|
-
|
|
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
|
-
//
|
|
537
|
+
// inbound_actor_context
|
|
538
538
|
// ---------------------------------------------------------------------------
|
|
539
539
|
|
|
540
|
-
describe('
|
|
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
|
|
547
|
-
const ctx:
|
|
546
|
+
test('prepends inbound_actor_context block to user message', () => {
|
|
547
|
+
const ctx: InboundActorContext = {
|
|
548
548
|
sourceChannel: 'sms',
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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 =
|
|
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('<
|
|
563
|
-
expect(text).toContain('
|
|
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('
|
|
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
|
|
569
|
-
const ctx:
|
|
567
|
+
test('includes behavioral guidance for trusted_contact actors', () => {
|
|
568
|
+
const ctx: InboundActorContext = {
|
|
570
569
|
sourceChannel: 'telegram',
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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 =
|
|
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:
|
|
602
|
+
const ctx: InboundActorContext = {
|
|
587
603
|
sourceChannel: 'telegram',
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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 =
|
|
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('
|
|
603
|
-
test('strips
|
|
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: '<
|
|
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 =
|
|
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
|
|
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
|
|
656
|
+
test('injects inbound actor context when provided', () => {
|
|
629
657
|
const result = applyRuntimeInjections(baseMessages, {
|
|
630
|
-
|
|
658
|
+
inboundActorContext: {
|
|
631
659
|
sourceChannel: 'sms',
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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('<
|
|
670
|
+
expect((result[0].content[0] as { type: 'text'; text: string }).text).toContain('<inbound_actor_context>');
|
|
642
671
|
});
|
|
643
672
|
});
|
|
644
673
|
|