@vellumai/assistant 0.4.9 → 0.4.11

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 (116) hide show
  1. package/ARCHITECTURE.md +24 -0
  2. package/Dockerfile +1 -1
  3. package/README.md +16 -9
  4. package/package.json +1 -1
  5. package/src/__tests__/account-registry.test.ts +1 -0
  6. package/src/__tests__/actor-token-service.test.ts +1 -0
  7. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  8. package/src/__tests__/asset-materialize-tool.test.ts +7 -0
  9. package/src/__tests__/asset-search-tool.test.ts +7 -0
  10. package/src/__tests__/browser-fill-credential.test.ts +1 -0
  11. package/src/__tests__/call-start-guardian-guard.test.ts +1 -0
  12. package/src/__tests__/channel-approval-routes.test.ts +29 -0
  13. package/src/__tests__/channel-guardian.test.ts +2143 -1546
  14. package/src/__tests__/channel-retry-sweep.test.ts +169 -14
  15. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -0
  16. package/src/__tests__/computer-use-tools.test.ts +1 -0
  17. package/src/__tests__/contacts-tools.test.ts +1 -0
  18. package/src/__tests__/conversation-attention-telegram.test.ts +1 -0
  19. package/src/__tests__/credential-policy-validate.test.ts +97 -0
  20. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  21. package/src/__tests__/credential-vault-unit.test.ts +1 -0
  22. package/src/__tests__/credential-vault.test.ts +1 -0
  23. package/src/__tests__/delete-managed-skill-tool.test.ts +1 -0
  24. package/src/__tests__/file-edit-tool.test.ts +1 -0
  25. package/src/__tests__/file-read-tool.test.ts +1 -0
  26. package/src/__tests__/file-write-tool.test.ts +1 -0
  27. package/src/__tests__/followup-tools.test.ts +1 -0
  28. package/src/__tests__/gateway-only-guard.test.ts +1 -1
  29. package/src/__tests__/guardian-control-plane-policy.test.ts +5 -4
  30. package/src/__tests__/guardian-grant-minting.test.ts +3 -0
  31. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +4 -3
  32. package/src/__tests__/guardian-routing-state.test.ts +8 -0
  33. package/src/__tests__/headless-browser-interactions.test.ts +1 -0
  34. package/src/__tests__/headless-browser-navigate.test.ts +1 -0
  35. package/src/__tests__/headless-browser-read-tools.test.ts +1 -0
  36. package/src/__tests__/headless-browser-snapshot.test.ts +1 -0
  37. package/src/__tests__/host-file-edit-tool.test.ts +1 -0
  38. package/src/__tests__/host-file-read-tool.test.ts +1 -0
  39. package/src/__tests__/host-file-write-tool.test.ts +1 -0
  40. package/src/__tests__/host-shell-tool.test.ts +1 -0
  41. package/src/__tests__/lifecycle-docs-guard.test.ts +207 -0
  42. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -0
  43. package/src/__tests__/media-reuse-story.e2e.test.ts +8 -0
  44. package/src/__tests__/messaging-send-tool.test.ts +1 -0
  45. package/src/__tests__/playbook-execution.test.ts +1 -0
  46. package/src/__tests__/playbook-tools.test.ts +1 -0
  47. package/src/__tests__/relay-server.test.ts +4 -0
  48. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -0
  49. package/src/__tests__/schedule-tools.test.ts +1 -0
  50. package/src/__tests__/secret-onetime-send.test.ts +4 -0
  51. package/src/__tests__/secret-scanner-executor.test.ts +2 -0
  52. package/src/__tests__/send-notification-tool.test.ts +2 -0
  53. package/src/__tests__/shell-credential-ref.test.ts +1 -0
  54. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -0
  55. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  56. package/src/__tests__/skill-load-tool.test.ts +1 -0
  57. package/src/__tests__/skill-script-runner-host.test.ts +1 -0
  58. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -0
  59. package/src/__tests__/skill-script-runner.test.ts +1 -0
  60. package/src/__tests__/skill-tool-factory.test.ts +1 -0
  61. package/src/__tests__/subagent-tools.test.ts +1 -1
  62. package/src/__tests__/swarm-recursion.test.ts +1 -0
  63. package/src/__tests__/swarm-session-integration.test.ts +1 -0
  64. package/src/__tests__/swarm-tool.test.ts +1 -0
  65. package/src/__tests__/task-management-tools.test.ts +1 -0
  66. package/src/__tests__/task-tools.test.ts +1 -0
  67. package/src/__tests__/terminal-tools.test.ts +1 -0
  68. package/src/__tests__/tool-approval-handler.test.ts +2 -2
  69. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  70. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  71. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  72. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -0
  73. package/src/__tests__/tool-executor.test.ts +1 -0
  74. package/src/__tests__/trust-context-guards.test.ts +218 -0
  75. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +6 -0
  76. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +6 -0
  77. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -0
  78. package/src/__tests__/trusted-contact-verification.test.ts +1 -0
  79. package/src/__tests__/view-image-tool.test.ts +1 -0
  80. package/src/calls/guardian-dispatch.ts +4 -4
  81. package/src/cli/mcp.ts +183 -3
  82. package/src/config/bundled-skills/agentmail/SKILL.md +4 -4
  83. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +1 -0
  84. package/src/config/bundled-skills/phone-calls/SKILL.md +17 -119
  85. package/src/config/system-prompt.ts +4 -2
  86. package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
  87. package/src/daemon/computer-use-session.ts +1 -0
  88. package/src/daemon/session-agent-loop.ts +1 -1
  89. package/src/daemon/session-memory.ts +2 -2
  90. package/src/daemon/session-runtime-assembly.ts +2 -2
  91. package/src/daemon/session-tool-setup.ts +1 -1
  92. package/src/mcp/client.ts +55 -6
  93. package/src/mcp/manager.ts +9 -0
  94. package/src/mcp/mcp-oauth-provider.ts +347 -0
  95. package/src/memory/channel-delivery-store.ts +1 -0
  96. package/src/memory/db-init.ts +4 -0
  97. package/src/memory/delivery-status.ts +43 -0
  98. package/src/memory/guardian-bindings.ts +3 -3
  99. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +108 -0
  100. package/src/memory/migrations/index.ts +1 -0
  101. package/src/memory/migrations/registry.ts +6 -0
  102. package/src/memory/schema.ts +1 -1
  103. package/src/runtime/actor-trust-resolver.ts +13 -4
  104. package/src/runtime/channel-retry-sweep.ts +31 -14
  105. package/src/runtime/guardian-context-resolver.ts +25 -64
  106. package/src/runtime/guardian-outbound-actions.ts +399 -108
  107. package/src/runtime/guardian-vellum-migration.ts +1 -23
  108. package/src/runtime/guardian-verification-templates.ts +66 -30
  109. package/src/runtime/local-actor-identity.ts +4 -6
  110. package/src/runtime/middleware/actor-token.ts +2 -8
  111. package/src/runtime/routes/channel-route-shared.ts +0 -1
  112. package/src/runtime/routes/inbound-message-handler.ts +3 -4
  113. package/src/runtime/tool-grant-request-helper.ts +1 -1
  114. package/src/tools/credentials/policy-validate.ts +22 -0
  115. package/src/tools/guardian-control-plane-policy.ts +2 -2
  116. package/src/tools/types.ts +1 -1
@@ -74,6 +74,7 @@ function makeContext(): ToolContext {
74
74
  workingDir: '/tmp',
75
75
  sessionId: 'test-session',
76
76
  conversationId: 'test-conversation',
77
+ guardianTrustClass: 'guardian',
77
78
  };
78
79
  }
79
80
 
@@ -0,0 +1,207 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ import { describe, expect, it } from 'bun:test';
6
+
7
+ /**
8
+ * Guard test: prevent stale lifecycle instructions from being reintroduced
9
+ * into documentation. The canonical lifecycle commands are `vellum wake`,
10
+ * `vellum ps`, and `vellum sleep`. Repo-local slash commands live in
11
+ * `.claude/skills/`, not `.claude/commands/`.
12
+ *
13
+ * See AGENTS.md for the conventions these tests enforce.
14
+ */
15
+
16
+ const REPO_ROOT = join(import.meta.dir, '../../..');
17
+
18
+ describe('lifecycle docs guard', () => {
19
+ it('repo-local commands live in skills directory, not commands directory', () => {
20
+ const staleLocations = [
21
+ '.claude/commands/update.md',
22
+ '.claude/commands/release.md',
23
+ ];
24
+
25
+ const violations = staleLocations.filter((p) =>
26
+ existsSync(join(REPO_ROOT, p)),
27
+ );
28
+
29
+ if (violations.length > 0) {
30
+ const message = [
31
+ 'Found repo-local commands in .claude/commands/ — they should live in .claude/skills/.',
32
+ '',
33
+ 'Stale files:',
34
+ ...violations.map((f) => ` - ${f}`),
35
+ '',
36
+ 'Move them to .claude/skills/<name>/SKILL.md instead.',
37
+ ].join('\n');
38
+
39
+ expect(violations, message).toEqual([]);
40
+ }
41
+
42
+ // Verify the correct locations exist
43
+ const expectedLocations = [
44
+ '.claude/skills/update/SKILL.md',
45
+ '.claude/skills/release/SKILL.md',
46
+ ];
47
+
48
+ const missing = expectedLocations.filter(
49
+ (p) => !existsSync(join(REPO_ROOT, p)),
50
+ );
51
+
52
+ if (missing.length > 0) {
53
+ const message = [
54
+ 'Expected repo-local skill files are missing:',
55
+ ...missing.map((f) => ` - ${f}`),
56
+ ].join('\n');
57
+
58
+ expect(missing, message).toEqual([]);
59
+ }
60
+ });
61
+
62
+ it('key docs reference vellum lifecycle commands', () => {
63
+ const checks: Array<{
64
+ file: string;
65
+ pattern: string;
66
+ description: string;
67
+ }> = [
68
+ {
69
+ file: 'README.md',
70
+ pattern: 'vellum wake\\|vellum ps\\|vellum sleep',
71
+ description:
72
+ 'README.md should mention vellum wake, vellum ps, or vellum sleep',
73
+ },
74
+ {
75
+ file: 'assistant/README.md',
76
+ pattern: 'vellum wake\\|vellum ps',
77
+ description:
78
+ 'assistant/README.md should mention vellum wake or vellum ps',
79
+ },
80
+ {
81
+ file: 'AGENTS.md',
82
+ pattern: 'vellum ps\\|vellum sleep\\|vellum wake',
83
+ description:
84
+ 'AGENTS.md should mention vellum ps, vellum sleep, or vellum wake in the /update command description',
85
+ },
86
+ ];
87
+
88
+ const failures: string[] = [];
89
+
90
+ for (const check of checks) {
91
+ try {
92
+ execSync(`git grep -q '${check.pattern}' -- '${check.file}'`, {
93
+ encoding: 'utf-8',
94
+ cwd: REPO_ROOT,
95
+ });
96
+ } catch {
97
+ failures.push(check.description);
98
+ }
99
+ }
100
+
101
+ if (failures.length > 0) {
102
+ const message = [
103
+ 'Key docs are missing vellum lifecycle command references:',
104
+ '',
105
+ ...failures.map((f) => ` - ${f}`),
106
+ '',
107
+ 'These docs should reference vellum CLI lifecycle commands (wake/ps/sleep).',
108
+ ].join('\n');
109
+
110
+ expect(failures, message).toEqual([]);
111
+ }
112
+ });
113
+
114
+ it('no docs use stale daemon startup as primary instruction', () => {
115
+ // Files that are allowed to contain these patterns
116
+ const allowedPrefixes = [
117
+ 'cli/', // CLI source code
118
+ 'assistant/src/', // assistant runtime source
119
+ 'CLAUDE.md', // project instructions
120
+ 'AGENTS.md', // agent conventions (may reference patterns for context)
121
+ ];
122
+
123
+ const stalePatterns = [
124
+ {
125
+ pattern: 'bun run src/index.ts daemon start',
126
+ label: 'bun run src/index.ts daemon start',
127
+ },
128
+ ];
129
+
130
+ const violations: string[] = [];
131
+
132
+ for (const { pattern, label } of stalePatterns) {
133
+ let grepOutput = '';
134
+ try {
135
+ grepOutput = execSync(
136
+ `git grep -n '${pattern}' -- '*.md'`,
137
+ { encoding: 'utf-8', cwd: REPO_ROOT },
138
+ ).trim();
139
+ } catch {
140
+ // No matches — happy path
141
+ continue;
142
+ }
143
+
144
+ const lines = grepOutput.split('\n').filter((l) => l.length > 0);
145
+
146
+ for (const line of lines) {
147
+ const filePath = line.split(':')[0];
148
+
149
+ // Skip allowed file prefixes
150
+ if (allowedPrefixes.some((prefix) => filePath.startsWith(prefix))) {
151
+ continue;
152
+ }
153
+
154
+ // Skip test files
155
+ if (filePath.includes('__tests__') || filePath.endsWith('.test.ts')) {
156
+ continue;
157
+ }
158
+
159
+ // Check if this specific occurrence is inside a <details> section or
160
+ // a "Development" / "raw bun commands" context by examining only the
161
+ // 10 lines before this match. We extract the line number from the
162
+ // grep output (format "filePath:lineNum:content") and use sed to
163
+ // get a targeted range, so each match is evaluated independently.
164
+ const parts = line.split(':');
165
+ const lineNum = parseInt(parts[1], 10);
166
+
167
+ if (!Number.isNaN(lineNum)) {
168
+ try {
169
+ const startLine = Math.max(1, lineNum - 10);
170
+ const context = execSync(
171
+ `sed -n '${startLine},${lineNum}p' '${filePath}'`,
172
+ { encoding: 'utf-8', cwd: REPO_ROOT },
173
+ );
174
+
175
+ const contextLower = context.toLowerCase();
176
+ const isInDetails = contextLower.includes('<details>');
177
+ const isDevContext =
178
+ contextLower.includes('development:') ||
179
+ contextLower.includes('low-level development') ||
180
+ contextLower.includes('raw bun commands');
181
+
182
+ if (isInDetails || isDevContext) {
183
+ continue;
184
+ }
185
+ } catch {
186
+ // If context extraction fails, treat as a violation
187
+ }
188
+ }
189
+
190
+ violations.push(`${filePath}: contains "${label}" as primary instruction`);
191
+ }
192
+ }
193
+
194
+ if (violations.length > 0) {
195
+ const message = [
196
+ 'Found docs using stale daemon startup patterns as primary instructions.',
197
+ 'Use `vellum wake` / `vellum sleep` instead. Raw bun commands are acceptable',
198
+ 'only in collapsed <details> sections or dev-only contexts.',
199
+ '',
200
+ 'Violations:',
201
+ ...violations.map((v) => ` - ${v}`),
202
+ ].join('\n');
203
+
204
+ expect(violations, message).toEqual([]);
205
+ }
206
+ });
207
+ });
@@ -84,6 +84,7 @@ function makeContext(): ToolContext {
84
84
  workingDir: '/tmp',
85
85
  sessionId: 'test-session',
86
86
  conversationId: 'test-conversation',
87
+ guardianTrustClass: 'guardian',
87
88
  };
88
89
  }
89
90
 
@@ -235,6 +235,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
235
235
  workingDir: sandboxDir,
236
236
  sessionId: 'sess-story',
237
237
  conversationId: threadB.id,
238
+ guardianTrustClass: 'guardian',
238
239
  };
239
240
 
240
241
  const result = await assetSearchTool.execute(
@@ -253,6 +254,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
253
254
  workingDir: sandboxDir,
254
255
  sessionId: 'sess-story',
255
256
  conversationId: threadB.id,
257
+ guardianTrustClass: 'guardian',
256
258
  };
257
259
 
258
260
  const result = await assetSearchTool.execute(
@@ -269,6 +271,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
269
271
  workingDir: sandboxDir,
270
272
  sessionId: 'sess-story',
271
273
  conversationId: threadB.id,
274
+ guardianTrustClass: 'guardian',
272
275
  };
273
276
 
274
277
  const result = await assetMaterializeTool.execute(
@@ -296,6 +299,7 @@ describe('Story E2E: selfie yesterday -> generated image today', () => {
296
299
  workingDir: sandboxDir,
297
300
  sessionId: 'sess-story',
298
301
  conversationId: threadB.id,
302
+ guardianTrustClass: 'guardian',
299
303
  };
300
304
 
301
305
  // Step 3a: Search for the selfie
@@ -486,6 +490,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
486
490
  workingDir: sandboxDir,
487
491
  sessionId: 'sess-priv-test',
488
492
  conversationId: standardThread.id,
493
+ guardianTrustClass: 'guardian',
489
494
  };
490
495
 
491
496
  const result = await assetSearchTool.execute(
@@ -510,6 +515,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
510
515
  workingDir: sandboxDir,
511
516
  sessionId: 'sess-priv-test',
512
517
  conversationId: standardThread.id,
518
+ guardianTrustClass: 'guardian',
513
519
  };
514
520
 
515
521
  const result = await assetMaterializeTool.execute(
@@ -533,6 +539,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
533
539
  workingDir: sandboxDir,
534
540
  sessionId: 'sess-priv-test',
535
541
  conversationId: privateThread.id,
542
+ guardianTrustClass: 'guardian',
536
543
  };
537
544
 
538
545
  const searchResult = await assetSearchTool.execute(
@@ -563,6 +570,7 @@ describe('Private-thread variant: cross-thread media blocking', () => {
563
570
  workingDir: sandboxDir,
564
571
  sessionId: 'sess-priv-test',
565
572
  conversationId: privateThreadB.id,
573
+ guardianTrustClass: 'guardian',
566
574
  };
567
575
 
568
576
  const searchResult = await assetSearchTool.execute(
@@ -48,6 +48,7 @@ describe('messaging-send tool', () => {
48
48
  sessionId: 'sess-1',
49
49
  conversationId: 'conv-1',
50
50
  assistantId: 'ast-alpha',
51
+ guardianTrustClass: 'guardian' as const,
51
52
  },
52
53
  );
53
54
 
@@ -69,6 +69,7 @@ const ctx: ToolContext = {
69
69
  workingDir: '/tmp',
70
70
  sessionId: 'test-session',
71
71
  conversationId: 'test-conversation',
72
+ guardianTrustClass: 'guardian',
72
73
  };
73
74
 
74
75
  function insertPlaybookRow(overrides: Partial<{
@@ -61,6 +61,7 @@ const ctx: ToolContext = {
61
61
  workingDir: '/tmp',
62
62
  sessionId: 'test-session',
63
63
  conversationId: 'test-conversation',
64
+ guardianTrustClass: 'guardian',
64
65
  };
65
66
 
66
67
  function clearPlaybooks(): void {
@@ -1277,6 +1277,7 @@ describe('relay-server', () => {
1277
1277
  channel: 'voice',
1278
1278
  guardianExternalUserId: '+15550001111',
1279
1279
  guardianDeliveryChatId: '+15550001111',
1280
+ guardianPrincipalId: '+15550001111',
1280
1281
  });
1281
1282
 
1282
1283
  mockSendMessage.mockImplementation(createMockProviderResponse(['Hello there.']));
@@ -1313,6 +1314,7 @@ describe('relay-server', () => {
1313
1314
  channel: 'voice',
1314
1315
  guardianExternalUserId: '+15550009999',
1315
1316
  guardianDeliveryChatId: '+15550009999',
1317
+ guardianPrincipalId: '+15550009999',
1316
1318
  });
1317
1319
  addTrustedVoiceContact('+15550002222', 'self');
1318
1320
 
@@ -1360,6 +1362,7 @@ describe('relay-server', () => {
1360
1362
  channel: 'voice',
1361
1363
  guardianExternalUserId: '+15550001111',
1362
1364
  guardianDeliveryChatId: '+15550001111',
1365
+ guardianPrincipalId: '+15550001111',
1363
1366
  });
1364
1367
 
1365
1368
  mockSendMessage.mockImplementation(createMockProviderResponse(['Hello there.']));
@@ -1404,6 +1407,7 @@ describe('relay-server', () => {
1404
1407
  channel: 'telegram',
1405
1408
  guardianExternalUserId: 'tg-guardian-user',
1406
1409
  guardianDeliveryChatId: 'tg-guardian-chat',
1410
+ guardianPrincipalId: 'tg-guardian-user',
1407
1411
  });
1408
1412
 
1409
1413
  // Number matches the configured owner number, but there is no active
@@ -30,6 +30,7 @@ function makeContext(): ToolContext {
30
30
  workingDir: '/tmp',
31
31
  sessionId: 'test-session',
32
32
  conversationId: 'test-conversation',
33
+ guardianTrustClass: 'guardian',
33
34
  };
34
35
  }
35
36
 
@@ -56,6 +56,7 @@ const ctx: ToolContext = {
56
56
  workingDir: '/tmp',
57
57
  sessionId: 'test-session',
58
58
  conversationId: 'test-conversation',
59
+ guardianTrustClass: 'guardian',
59
60
  };
60
61
 
61
62
  // ── schedule_create ─────────────────────────────────────────────────
@@ -61,6 +61,7 @@ describe('one-time send override', () => {
61
61
  workingDir: '/tmp',
62
62
  sessionId: 's1',
63
63
  conversationId: 'c1',
64
+ guardianTrustClass: 'guardian' as const,
64
65
  requestSecret: async () => ({ value: 'v1', delivery: 'transient_send' as const }),
65
66
  };
66
67
 
@@ -80,6 +81,7 @@ describe('one-time send override', () => {
80
81
  workingDir: '/tmp',
81
82
  sessionId: 's1',
82
83
  conversationId: 'c1',
84
+ guardianTrustClass: 'guardian' as const,
83
85
  requestSecret: async () => ({ value: 'v1', delivery: 'transient_send' as const }),
84
86
  };
85
87
 
@@ -99,6 +101,7 @@ describe('one-time send override', () => {
99
101
  workingDir: '/tmp',
100
102
  sessionId: 's1',
101
103
  conversationId: 'c1',
104
+ guardianTrustClass: 'guardian' as const,
102
105
  requestSecret: async () => ({ value: 'v1', delivery: 'store' as const }),
103
106
  };
104
107
 
@@ -118,6 +121,7 @@ describe('one-time send override', () => {
118
121
  workingDir: '/tmp',
119
122
  sessionId: 's1',
120
123
  conversationId: 'c1',
124
+ guardianTrustClass: 'guardian' as const,
121
125
  requestSecret: async () => ({ value: secretVal, delivery: 'transient_send' as const }),
122
126
  };
123
127
 
@@ -95,6 +95,7 @@ function makeContext(overrides?: Partial<{
95
95
  workingDir: '/tmp',
96
96
  sessionId: 'test-session',
97
97
  conversationId: 'test-conversation',
98
+ guardianTrustClass: 'guardian' as const,
98
99
  onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
99
100
  auditListener(event);
100
101
  return overrides?.onToolLifecycleEvent?.(event);
@@ -313,6 +314,7 @@ describe('Secret scanner executor integration', () => {
313
314
  workingDir: '/tmp',
314
315
  sessionId: 'test-session',
315
316
  conversationId: 'test-conversation',
317
+ guardianTrustClass: 'guardian' as const,
316
318
  };
317
319
 
318
320
  const result = await executor.execute('file_read', {}, ctx);
@@ -33,6 +33,7 @@ describe('send-notification tool', () => {
33
33
  sessionId: 'sess-1',
34
34
  conversationId: 'conv-1',
35
35
  assistantId: 'ast-alpha',
36
+ guardianTrustClass: 'guardian' as const,
36
37
  },
37
38
  );
38
39
 
@@ -76,6 +77,7 @@ describe('send-notification tool', () => {
76
77
  sessionId: 'sess-1',
77
78
  conversationId: 'conv-1',
78
79
  assistantId: 'ast-alpha',
80
+ guardianTrustClass: 'guardian' as const,
79
81
  },
80
82
  );
81
83
 
@@ -77,6 +77,7 @@ const ctx: ToolContext = {
77
77
  workingDir: '/tmp',
78
78
  sessionId: 'test-session',
79
79
  conversationId: 'test-conv',
80
+ guardianTrustClass: 'guardian',
80
81
  };
81
82
 
82
83
  beforeEach(() => {
@@ -127,6 +127,7 @@ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
127
127
  workingDir: '/tmp',
128
128
  sessionId: 'test-session',
129
129
  conversationId: 'test-conv',
130
+ guardianTrustClass: 'guardian',
130
131
  ...overrides,
131
132
  };
132
133
  }
@@ -81,6 +81,7 @@ async function executeSkillLoad(input: Record<string, unknown>): Promise<{ conte
81
81
  workingDir: '/tmp',
82
82
  sessionId: 'session-1',
83
83
  conversationId: 'conversation-1',
84
+ guardianTrustClass: 'guardian',
84
85
  });
85
86
  return { content: result.content, isError: result.isError };
86
87
  }
@@ -76,6 +76,7 @@ async function executeSkillLoad(input: Record<string, unknown>): Promise<{ conte
76
76
  workingDir: '/tmp',
77
77
  sessionId: 'session-1',
78
78
  conversationId: 'conversation-1',
79
+ guardianTrustClass: 'guardian',
79
80
  });
80
81
  return { content: result.content, isError: result.isError };
81
82
  }
@@ -17,6 +17,7 @@ function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
17
17
  workingDir: '/tmp',
18
18
  sessionId: 'test-session',
19
19
  conversationId: 'test-conversation',
20
+ guardianTrustClass: 'guardian',
20
21
  ...overrides,
21
22
  };
22
23
  }
@@ -63,6 +63,7 @@ function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
63
63
  workingDir: '/tmp',
64
64
  sessionId: 'test-session',
65
65
  conversationId: 'test-conversation',
66
+ guardianTrustClass: 'guardian',
66
67
  ...overrides,
67
68
  };
68
69
  }
@@ -17,6 +17,7 @@ const ctx: ToolContext = {
17
17
  workingDir: '/tmp',
18
18
  sessionId: 'test-session',
19
19
  conversationId: 'test-conversation',
20
+ guardianTrustClass: 'guardian',
20
21
  };
21
22
 
22
23
  function makeSkillDir(name: string): string {
@@ -36,6 +36,7 @@ function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
36
36
  workingDir: '/tmp',
37
37
  sessionId: 'test-session',
38
38
  conversationId: 'test-conversation',
39
+ guardianTrustClass: 'guardian',
39
40
  ...overrides,
40
41
  };
41
42
  }
@@ -88,7 +88,7 @@ function injectSubagent(
88
88
  }
89
89
 
90
90
  function makeContext(sessionId: string, extras: Record<string, unknown> = {}) {
91
- return { workingDir: '/tmp', sessionId, conversationId: 'conv-1', ...extras };
91
+ return { workingDir: '/tmp', sessionId, conversationId: 'conv-1', guardianTrustClass: 'guardian' as const, ...extras } as import('../tools/types.js').ToolContext;
92
92
  }
93
93
 
94
94
  // ── Tool definitions ────────────────────────────────────────────────
@@ -87,6 +87,7 @@ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
87
87
  sessionId: 'test-session',
88
88
  conversationId: 'test-conv',
89
89
  workingDir: '/tmp/test',
90
+ guardianTrustClass: 'guardian',
90
91
  onOutput: () => {},
91
92
  ...overrides,
92
93
  } as ToolContext;
@@ -79,6 +79,7 @@ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
79
79
  sessionId: 'test-session',
80
80
  conversationId: 'test-conv',
81
81
  workingDir: '/tmp/test',
82
+ guardianTrustClass: 'guardian',
82
83
  onOutput: () => {},
83
84
  ...overrides,
84
85
  } as ToolContext;
@@ -74,6 +74,7 @@ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
74
74
  return {
75
75
  sessionId: 'test-session',
76
76
  workingDir: '/tmp/test',
77
+ guardianTrustClass: 'guardian',
77
78
  onOutput: () => {},
78
79
  ...overrides,
79
80
  } as ToolContext;
@@ -62,6 +62,7 @@ const ctx: ToolContext = {
62
62
  workingDir: '/tmp',
63
63
  sessionId: 'test-session',
64
64
  conversationId: 'test-conversation',
65
+ guardianTrustClass: 'guardian',
65
66
  };
66
67
 
67
68
  function clearTables() {
@@ -84,6 +84,7 @@ const stubContext: ToolContext = {
84
84
  workingDir: '/tmp',
85
85
  sessionId: 'test-session',
86
86
  conversationId: 'test-conversation',
87
+ guardianTrustClass: 'guardian',
87
88
  };
88
89
 
89
90
  // ── task_save ────────────────────────────────────────────────────────
@@ -573,6 +573,7 @@ describe('Shell tool input validation', () => {
573
573
  workingDir: testTmpDir,
574
574
  sessionId: 'test-session-1',
575
575
  conversationId: 'test-conv-1',
576
+ guardianTrustClass: 'guardian' as const,
576
577
  onOutput: () => {},
577
578
  };
578
579
 
@@ -286,11 +286,11 @@ describe('ToolApprovalHandler / pre-exec gate grant check', () => {
286
286
  expect(result.allowed).toBe(true);
287
287
  });
288
288
 
289
- test('undefined actor role (desktop) bypasses grant check', async () => {
289
+ test('guardian actor role (desktop) bypasses grant check', async () => {
290
290
  const toolName = 'bash';
291
291
  const input = { command: 'deploy' };
292
292
 
293
- const context = makeContext({ guardianTrustClass: undefined });
293
+ const context = makeContext({ guardianTrustClass: 'guardian' });
294
294
  const result = await handler.checkPreExecutionGates(
295
295
  toolName, input, context, 'host', 'high', Date.now(), emitLifecycleEvent,
296
296
  );
@@ -101,6 +101,7 @@ function makeToolContext(workingDir: string, signal?: AbortSignal) {
101
101
  workingDir,
102
102
  sessionId: 'test-session',
103
103
  conversationId: 'test-conv',
104
+ guardianTrustClass: 'guardian' as const,
104
105
  signal,
105
106
  };
106
107
  }
@@ -403,6 +403,7 @@ describe('Tool execution pipeline benchmark', () => {
403
403
  workingDir: '/tmp',
404
404
  sessionId: 'bench-session',
405
405
  conversationId: 'bench-conv',
406
+ guardianTrustClass: 'guardian',
406
407
  };
407
408
 
408
409
  function makeTool(name: string, sleepMs: number): Tool {
@@ -138,6 +138,7 @@ function makeContext(events: ToolLifecycleEvent[]) {
138
138
  workingDir: '/tmp/project',
139
139
  sessionId: 'session-1',
140
140
  conversationId: 'conversation-1',
141
+ guardianTrustClass: 'guardian' as const,
141
142
  onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
142
143
  events.push(event);
143
144
  },
@@ -424,6 +425,7 @@ describe('ToolExecutor lifecycle events', () => {
424
425
  workingDir: '/tmp/project',
425
426
  sessionId: 'session-1',
426
427
  conversationId: 'conversation-1',
428
+ guardianTrustClass: 'guardian',
427
429
  onToolLifecycleEvent: () => new Promise<void>(() => {}),
428
430
  });
429
431
 
@@ -125,6 +125,7 @@ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
125
125
  workingDir: '/tmp/project',
126
126
  sessionId: 'session-integration',
127
127
  conversationId: 'conversation-integration',
128
+ guardianTrustClass: 'guardian',
128
129
  ...overrides,
129
130
  };
130
131
  }
@@ -106,6 +106,7 @@ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
106
106
  workingDir: '/tmp/project',
107
107
  sessionId: 'session-1',
108
108
  conversationId: 'conversation-1',
109
+ guardianTrustClass: 'guardian',
109
110
  ...overrides,
110
111
  };
111
112
  }