@vellumai/assistant 0.3.13 → 0.3.15

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 (57) hide show
  1. package/ARCHITECTURE.md +17 -3
  2. package/Dockerfile +1 -1
  3. package/README.md +2 -0
  4. package/docs/architecture/scheduling.md +81 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +22 -0
  7. package/src/__tests__/channel-policy.test.ts +19 -0
  8. package/src/__tests__/guardian-control-plane-policy.test.ts +582 -0
  9. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  10. package/src/__tests__/intent-routing.test.ts +22 -0
  11. package/src/__tests__/ipc-snapshot.test.ts +10 -0
  12. package/src/__tests__/notification-routing-intent.test.ts +185 -0
  13. package/src/__tests__/recording-handler.test.ts +191 -31
  14. package/src/__tests__/recording-intent-fallback.test.ts +180 -0
  15. package/src/__tests__/recording-intent-handler.test.ts +597 -74
  16. package/src/__tests__/recording-intent.test.ts +738 -342
  17. package/src/__tests__/recording-state-machine.test.ts +1109 -0
  18. package/src/__tests__/reminder-store.test.ts +20 -18
  19. package/src/__tests__/reminder.test.ts +2 -1
  20. package/src/channels/config.ts +1 -1
  21. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -11
  22. package/src/config/bundled-skills/screen-recording/SKILL.md +91 -12
  23. package/src/config/system-prompt.ts +5 -0
  24. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
  25. package/src/daemon/handlers/config-channels.ts +6 -6
  26. package/src/daemon/handlers/index.ts +1 -1
  27. package/src/daemon/handlers/misc.ts +258 -102
  28. package/src/daemon/handlers/recording.ts +417 -5
  29. package/src/daemon/handlers/sessions.ts +142 -68
  30. package/src/daemon/ipc-contract/computer-use.ts +23 -3
  31. package/src/daemon/ipc-contract/messages.ts +3 -1
  32. package/src/daemon/ipc-contract/shared.ts +6 -0
  33. package/src/daemon/ipc-contract-inventory.json +2 -0
  34. package/src/daemon/lifecycle.ts +2 -0
  35. package/src/daemon/recording-executor.ts +180 -0
  36. package/src/daemon/recording-intent-fallback.ts +132 -0
  37. package/src/daemon/recording-intent.ts +306 -15
  38. package/src/daemon/session-tool-setup.ts +4 -0
  39. package/src/memory/conversation-attention-store.ts +5 -5
  40. package/src/notifications/README.md +69 -1
  41. package/src/notifications/adapters/sms.ts +80 -0
  42. package/src/notifications/broadcaster.ts +1 -0
  43. package/src/notifications/copy-composer.ts +3 -3
  44. package/src/notifications/decision-engine.ts +70 -1
  45. package/src/notifications/decisions-store.ts +24 -0
  46. package/src/notifications/destination-resolver.ts +2 -1
  47. package/src/notifications/emit-signal.ts +35 -3
  48. package/src/notifications/signal.ts +6 -0
  49. package/src/notifications/types.ts +3 -0
  50. package/src/runtime/guardian-outbound-actions.ts +9 -9
  51. package/src/runtime/http-server.ts +7 -7
  52. package/src/runtime/routes/conversation-attention-routes.ts +3 -3
  53. package/src/runtime/routes/integration-routes.ts +5 -5
  54. package/src/schedule/scheduler.ts +15 -3
  55. package/src/tools/executor.ts +29 -0
  56. package/src/tools/guardian-control-plane-policy.ts +141 -0
  57. package/src/tools/types.ts +2 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Guardian control-plane policy \u2014 deterministic gate that prevents non-guardian
3
+ * and unverified_channel actors from invoking guardian verification endpoints
4
+ * conversationally via tools.
5
+ *
6
+ * Protected endpoints:
7
+ * /v1/integrations/guardian/challenge
8
+ * /v1/integrations/guardian/status
9
+ * /v1/integrations/guardian/outbound/start
10
+ * /v1/integrations/guardian/outbound/resend
11
+ * /v1/integrations/guardian/outbound/cancel
12
+ */
13
+
14
+ const GUARDIAN_ENDPOINT_PATHS = [
15
+ '/v1/integrations/guardian/challenge',
16
+ '/v1/integrations/guardian/status',
17
+ '/v1/integrations/guardian/outbound/start',
18
+ '/v1/integrations/guardian/outbound/resend',
19
+ '/v1/integrations/guardian/outbound/cancel',
20
+ ] as const;
21
+
22
+ /**
23
+ * Broad regex that catches any path targeting the guardian control-plane,
24
+ * even if the exact sub-path differs from the hardcoded list above.
25
+ * Anchored on a path separator so it won't match inside unrelated words.
26
+ */
27
+ const GUARDIAN_PATH_REGEX = /\/v1\/integrations\/guardian\//;
28
+
29
+ /** Tools whose `input.command` (string) may contain guardian endpoint paths. */
30
+ const COMMAND_TOOLS = new Set(['bash', 'host_bash']);
31
+
32
+ /** Tools whose `input.url` (string) may contain guardian endpoint paths. */
33
+ const URL_TOOLS = new Set(['network_request', 'web_fetch', 'browser_navigate']);
34
+
35
+ /**
36
+ * Normalize a string to defeat common URL obfuscation techniques before matching:
37
+ * - Decode percent-encoded characters (e.g. %2F → /)
38
+ * - Collapse consecutive slashes into a single slash (preserving protocol://)
39
+ * - Lowercase everything
40
+ */
41
+ function normalizeForMatching(value: string): string {
42
+ let normalized = value;
43
+ // Iteratively decode percent-encoding to handle double-encoding (%252F → %2F → /)
44
+ // Use per-sequence replacement instead of decodeURIComponent to avoid a single
45
+ // malformed sequence (e.g. %ZZ) preventing all other valid sequences from decoding.
46
+ let prev = '';
47
+ while (prev !== normalized) {
48
+ prev = normalized;
49
+ normalized = normalized.replace(/%[0-9a-fA-F]{2}/g, (match) => {
50
+ try {
51
+ return decodeURIComponent(match);
52
+ } catch {
53
+ return match;
54
+ }
55
+ });
56
+ }
57
+ // Collapse consecutive slashes (but preserve the double slash in protocol e.g. https://)
58
+ normalized = normalized.replace(/(?<!:)\/{2,}/g, '/');
59
+ return normalized.toLowerCase();
60
+ }
61
+
62
+ /**
63
+ * Check whether a string contains any of the guardian control-plane endpoint paths.
64
+ * Normalizes the input first to catch percent-encoding, double slashes, and case
65
+ * variations. Also matches a broad regex pattern to catch paths that target the
66
+ * guardian control-plane but aren't in the exact hardcoded list.
67
+ */
68
+ function containsGuardianEndpointPath(value: string): boolean {
69
+ const normalized = normalizeForMatching(value);
70
+ // Check exact hardcoded paths against the normalized string
71
+ for (const path of GUARDIAN_ENDPOINT_PATHS) {
72
+ if (normalized.includes(path)) return true;
73
+ }
74
+ // Broad pattern match to catch any /v1/integrations/guardian/... path
75
+ if (GUARDIAN_PATH_REGEX.test(normalized)) return true;
76
+ return false;
77
+ }
78
+
79
+ /**
80
+ * Conservative fallback for shell tools: detects when a command contains the
81
+ * key fragments of a guardian control-plane path even if they are not contiguous
82
+ * (e.g. constructed via shell variable expansion like `base=/v1/integrations; curl "$base/guardian/status"`).
83
+ *
84
+ * Only applied to bash/host_bash — URL tools pass structured URLs that cannot
85
+ * be split by shell expansion.
86
+ */
87
+ function containsGuardianFragments(command: string): boolean {
88
+ const lower = command.toLowerCase();
89
+ return lower.includes('/v1/integrations') && lower.includes('guardian');
90
+ }
91
+
92
+ /**
93
+ * Pure function that determines whether a tool invocation targets a guardian
94
+ * control-plane endpoint based on the tool name and its input.
95
+ */
96
+ export function isGuardianControlPlaneInvocation(
97
+ toolName: string,
98
+ input: Record<string, unknown>,
99
+ ): boolean {
100
+ if (COMMAND_TOOLS.has(toolName)) {
101
+ const command = input.command;
102
+ if (typeof command === 'string') {
103
+ // Primary: exact/normalized path matching
104
+ if (containsGuardianEndpointPath(command)) return true;
105
+ // Fallback: detect shell-expanded construction of guardian paths
106
+ if (containsGuardianFragments(command)) return true;
107
+ }
108
+ }
109
+
110
+ if (URL_TOOLS.has(toolName)) {
111
+ const url = input.url;
112
+ if (typeof url === 'string' && containsGuardianEndpointPath(url)) {
113
+ return true;
114
+ }
115
+ }
116
+
117
+ return false;
118
+ }
119
+
120
+ /**
121
+ * Enforce the guardian-only policy: if the invocation targets a guardian
122
+ * control-plane endpoint and the actor is not a guardian, deny.
123
+ */
124
+ export function enforceGuardianOnlyPolicy(
125
+ toolName: string,
126
+ input: Record<string, unknown>,
127
+ actorRole: string | undefined,
128
+ ): { denied: boolean; reason?: string } {
129
+ if (!isGuardianControlPlaneInvocation(toolName, input)) {
130
+ return { denied: false };
131
+ }
132
+
133
+ if (actorRole === 'guardian' || actorRole === undefined) {
134
+ return { denied: false };
135
+ }
136
+
137
+ return {
138
+ denied: true,
139
+ reason: 'Guardian verification control-plane actions are restricted to guardian users. This is a security restriction \u2014 please wait for the designated guardian to perform this action.',
140
+ };
141
+ }
@@ -136,6 +136,8 @@ export interface ToolContext {
136
136
  proxyApprovalCallback?: import('./network/script-proxy/types.js').ProxyApprovalCallback;
137
137
  /** Optional principal identifier propagated to sub-tool confirmation flows. */
138
138
  principal?: string;
139
+ /** Guardian actor role for the session — used by the guardian control-plane policy gate. */
140
+ guardianActorRole?: 'guardian' | 'non-guardian' | 'unverified_channel';
139
141
  }
140
142
 
141
143
  export interface DiffInfo {