@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.
- package/ARCHITECTURE.md +17 -3
- package/Dockerfile +1 -1
- package/README.md +2 -0
- package/docs/architecture/scheduling.md +81 -0
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +22 -0
- package/src/__tests__/channel-policy.test.ts +19 -0
- package/src/__tests__/guardian-control-plane-policy.test.ts +582 -0
- package/src/__tests__/guardian-outbound-http.test.ts +8 -8
- package/src/__tests__/intent-routing.test.ts +22 -0
- package/src/__tests__/ipc-snapshot.test.ts +10 -0
- package/src/__tests__/notification-routing-intent.test.ts +185 -0
- package/src/__tests__/recording-handler.test.ts +191 -31
- package/src/__tests__/recording-intent-fallback.test.ts +180 -0
- package/src/__tests__/recording-intent-handler.test.ts +597 -74
- package/src/__tests__/recording-intent.test.ts +738 -342
- package/src/__tests__/recording-state-machine.test.ts +1109 -0
- package/src/__tests__/reminder-store.test.ts +20 -18
- package/src/__tests__/reminder.test.ts +2 -1
- package/src/channels/config.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -11
- package/src/config/bundled-skills/screen-recording/SKILL.md +91 -12
- package/src/config/system-prompt.ts +5 -0
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
- package/src/daemon/handlers/config-channels.ts +6 -6
- package/src/daemon/handlers/index.ts +1 -1
- package/src/daemon/handlers/misc.ts +258 -102
- package/src/daemon/handlers/recording.ts +417 -5
- package/src/daemon/handlers/sessions.ts +142 -68
- package/src/daemon/ipc-contract/computer-use.ts +23 -3
- package/src/daemon/ipc-contract/messages.ts +3 -1
- package/src/daemon/ipc-contract/shared.ts +6 -0
- package/src/daemon/ipc-contract-inventory.json +2 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/recording-executor.ts +180 -0
- package/src/daemon/recording-intent-fallback.ts +132 -0
- package/src/daemon/recording-intent.ts +306 -15
- package/src/daemon/session-tool-setup.ts +4 -0
- package/src/memory/conversation-attention-store.ts +5 -5
- package/src/notifications/README.md +69 -1
- package/src/notifications/adapters/sms.ts +80 -0
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +70 -1
- package/src/notifications/decisions-store.ts +24 -0
- package/src/notifications/destination-resolver.ts +2 -1
- package/src/notifications/emit-signal.ts +35 -3
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/types.ts +3 -0
- package/src/runtime/guardian-outbound-actions.ts +9 -9
- package/src/runtime/http-server.ts +7 -7
- package/src/runtime/routes/conversation-attention-routes.ts +3 -3
- package/src/runtime/routes/integration-routes.ts +5 -5
- package/src/schedule/scheduler.ts +15 -3
- package/src/tools/executor.ts +29 -0
- package/src/tools/guardian-control-plane-policy.ts +141 -0
- 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
|
+
}
|
package/src/tools/types.ts
CHANGED
|
@@ -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 {
|