@vellumai/assistant 0.4.18 → 0.4.20
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/docs/runbook-trusted-contacts.md +5 -3
- package/package.json +1 -1
- package/src/__tests__/channel-approvals.test.ts +7 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/daemon-server-session-init.test.ts +2 -0
- package/src/__tests__/gmail-integration.test.ts +13 -4
- package/src/__tests__/handle-user-message-secret-resume.test.ts +7 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -0
- package/src/__tests__/ingress-reconcile.test.ts +13 -5
- package/src/__tests__/mcp-cli.test.ts +1 -1
- package/src/__tests__/recording-intent-handler.test.ts +9 -1
- package/src/__tests__/send-endpoint-busy.test.ts +8 -2
- package/src/__tests__/sms-messaging-provider.test.ts +4 -0
- package/src/__tests__/system-prompt.test.ts +18 -2
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/agent/loop.ts +324 -163
- package/src/cli/mcp.ts +81 -28
- package/src/config/bundled-skills/app-builder/SKILL.md +7 -5
- package/src/config/bundled-skills/app-builder/TOOLS.json +2 -2
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +6 -11
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -2
- package/src/config/bundled-skills/sms-setup/SKILL.md +8 -16
- package/src/config/bundled-skills/telegram-setup/SKILL.md +3 -3
- package/src/config/bundled-skills/trusted-contacts/SKILL.md +13 -25
- package/src/config/bundled-skills/twilio-setup/SKILL.md +13 -23
- package/src/config/system-prompt.ts +574 -518
- package/src/daemon/session-surfaces.ts +28 -0
- package/src/daemon/session.ts +255 -191
- package/src/daemon/tool-side-effects.ts +3 -13
- package/src/mcp/client.ts +2 -7
- package/src/security/secure-keys.ts +43 -3
- package/src/tools/apps/definitions.ts +5 -0
- package/src/tools/apps/executors.ts +18 -22
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/__tests__/response-tier.test.ts +0 -195
- package/src/daemon/response-tier.ts +0 -250
|
@@ -5,11 +5,13 @@ Operational procedures for inspecting, managing, and debugging the trusted conta
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
# Read the bearer token
|
|
9
|
-
TOKEN=$(cat ~/.vellum/http-token)
|
|
10
|
-
|
|
11
8
|
# Base URL (adjust if using a non-default port)
|
|
12
9
|
BASE=http://localhost:7830
|
|
10
|
+
|
|
11
|
+
# Bearer token: if running via the assistant's shell tools, $GATEWAY_AUTH_TOKEN
|
|
12
|
+
# is injected automatically. For manual operator use, mint a token via the CLI
|
|
13
|
+
# or use one from the daemon (e.g. from a recent shell env export).
|
|
14
|
+
TOKEN=$GATEWAY_AUTH_TOKEN
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
## 1. Inspect Trusted Contacts (Members)
|
package/package.json
CHANGED
|
@@ -132,9 +132,11 @@ describe("getChannelApprovalPrompt", () => {
|
|
|
132
132
|
const result = getChannelApprovalPrompt("conv-1");
|
|
133
133
|
expect(result).not.toBeNull();
|
|
134
134
|
expect(result!.promptText).toContain("shell");
|
|
135
|
-
expect(result!.actions).toHaveLength(
|
|
135
|
+
expect(result!.actions).toHaveLength(5);
|
|
136
136
|
expect(result!.actions.map((a) => a.id)).toEqual([
|
|
137
137
|
"approve_once",
|
|
138
|
+
"approve_10m",
|
|
139
|
+
"approve_thread",
|
|
138
140
|
"approve_always",
|
|
139
141
|
"reject",
|
|
140
142
|
]);
|
|
@@ -174,6 +176,8 @@ describe("getChannelApprovalPrompt", () => {
|
|
|
174
176
|
expect(result).not.toBeNull();
|
|
175
177
|
expect(result!.actions.map((a) => a.id)).toEqual([
|
|
176
178
|
"approve_once",
|
|
179
|
+
"approve_10m",
|
|
180
|
+
"approve_thread",
|
|
177
181
|
"approve_always",
|
|
178
182
|
"reject",
|
|
179
183
|
]);
|
|
@@ -189,6 +193,8 @@ describe("getChannelApprovalPrompt", () => {
|
|
|
189
193
|
expect(result).not.toBeNull();
|
|
190
194
|
expect(result!.actions.map((a) => a.id)).toEqual([
|
|
191
195
|
"approve_once",
|
|
196
|
+
"approve_10m",
|
|
197
|
+
"approve_thread",
|
|
192
198
|
"approve_always",
|
|
193
199
|
"reject",
|
|
194
200
|
]);
|
|
@@ -79,6 +79,14 @@ mock.module("../runtime/local-actor-identity.js", () => ({
|
|
|
79
79
|
}),
|
|
80
80
|
}));
|
|
81
81
|
|
|
82
|
+
mock.module("../runtime/guardian-context-resolver.js", () => ({
|
|
83
|
+
resolveGuardianContext: () => ({
|
|
84
|
+
trustClass: "guardian",
|
|
85
|
+
sourceChannel: "vellum",
|
|
86
|
+
}),
|
|
87
|
+
toGuardianRuntimeContext: (ctx: unknown) => ctx,
|
|
88
|
+
}));
|
|
89
|
+
|
|
82
90
|
import type { AuthContext } from "../runtime/auth/types.js";
|
|
83
91
|
import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
|
|
84
92
|
|
|
@@ -11,6 +11,13 @@ const toolsManifestPath = resolve(
|
|
|
11
11
|
"../config/bundled-skills/messaging/TOOLS.json",
|
|
12
12
|
);
|
|
13
13
|
const toolsManifest = JSON.parse(readFileSync(toolsManifestPath, "utf-8"));
|
|
14
|
+
const slackToolsManifestPath = resolve(
|
|
15
|
+
__dirname,
|
|
16
|
+
"../config/bundled-skills/slack/TOOLS.json",
|
|
17
|
+
);
|
|
18
|
+
const slackToolsManifest = JSON.parse(
|
|
19
|
+
readFileSync(slackToolsManifestPath, "utf-8"),
|
|
20
|
+
);
|
|
14
21
|
|
|
15
22
|
describe("Messaging tool contract", () => {
|
|
16
23
|
const expectedGmailToolNames = [
|
|
@@ -70,19 +77,21 @@ describe("Messaging tool contract", () => {
|
|
|
70
77
|
});
|
|
71
78
|
|
|
72
79
|
test("TOOLS.json manifest contains all expected slack_* tool names", () => {
|
|
73
|
-
const
|
|
80
|
+
const slackToolNames: string[] = slackToolsManifest.tools.map(
|
|
74
81
|
(t: { name: string }) => t.name,
|
|
75
82
|
);
|
|
76
83
|
for (const name of expectedSlackToolNames) {
|
|
77
|
-
expect(
|
|
84
|
+
expect(slackToolNames).toContain(name);
|
|
78
85
|
}
|
|
79
86
|
});
|
|
80
87
|
|
|
81
|
-
test("TOOLS.json
|
|
88
|
+
test("TOOLS.json manifests contain at least the expected number of tools", () => {
|
|
82
89
|
const expectedMinimum =
|
|
83
90
|
expectedGmailToolNames.length +
|
|
84
91
|
expectedMessagingToolNames.length +
|
|
85
92
|
expectedSlackToolNames.length;
|
|
86
|
-
|
|
93
|
+
const totalTools =
|
|
94
|
+
toolsManifest.tools.length + slackToolsManifest.tools.length;
|
|
95
|
+
expect(totalTools).toBeGreaterThanOrEqual(expectedMinimum);
|
|
87
96
|
});
|
|
88
97
|
});
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import * as net from "node:net";
|
|
2
2
|
import { describe, expect, mock, test } from "bun:test";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const actualEnv = await import("../config/env.js");
|
|
5
|
+
mock.module("../config/env.js", () => ({
|
|
6
|
+
...actualEnv,
|
|
7
|
+
isHttpAuthDisabled: () => true,
|
|
8
|
+
isMonitoringEnabled: () => false,
|
|
9
|
+
}));
|
|
5
10
|
|
|
6
11
|
const { handleUserMessage } = await import("../daemon/handlers/sessions.js");
|
|
7
12
|
|
|
@@ -47,6 +52,7 @@ describe("handleUserMessage secret redirect continuation", () => {
|
|
|
47
52
|
setAssistantId: () => {},
|
|
48
53
|
setChannelCapabilities: () => {},
|
|
49
54
|
setGuardianContext: () => {},
|
|
55
|
+
setAuthContext: () => {},
|
|
50
56
|
setCommandIntent: () => {},
|
|
51
57
|
updateClient: () => {},
|
|
52
58
|
emitActivityState: () => {},
|
|
@@ -116,6 +116,7 @@ interface TestSession {
|
|
|
116
116
|
setTurnInterfaceContext: (ctx: unknown) => void;
|
|
117
117
|
setAssistantId: (assistantId: string) => void;
|
|
118
118
|
setGuardianContext: (ctx: unknown) => void;
|
|
119
|
+
setAuthContext: (ctx: unknown) => void;
|
|
119
120
|
setCommandIntent: (intent: unknown) => void;
|
|
120
121
|
updateClient: (
|
|
121
122
|
sendToClient: (msg: ServerMessage) => void,
|
|
@@ -180,6 +181,7 @@ function makeSession(overrides: Partial<TestSession> = {}): TestSession {
|
|
|
180
181
|
setTurnInterfaceContext: () => {},
|
|
181
182
|
setAssistantId: () => {},
|
|
182
183
|
setGuardianContext: () => {},
|
|
184
|
+
setAuthContext: () => {},
|
|
183
185
|
setCommandIntent: () => {},
|
|
184
186
|
updateClient: () => {},
|
|
185
187
|
emitActivityState: () => {},
|
|
@@ -66,6 +66,13 @@ mock.module("../providers/registry.js", () => ({
|
|
|
66
66
|
initializeProviders: () => {},
|
|
67
67
|
}));
|
|
68
68
|
|
|
69
|
+
// Mock token service — triggerGatewayReconcile now uses mintDaemonDeliveryToken
|
|
70
|
+
// instead of readHttpToken for the Bearer token.
|
|
71
|
+
let mintedToken: string | null = null;
|
|
72
|
+
mock.module("../runtime/auth/token-service.js", () => ({
|
|
73
|
+
mintDaemonDeliveryToken: () => mintedToken ?? "test-delivery-token",
|
|
74
|
+
}));
|
|
75
|
+
|
|
69
76
|
import { handleIngressConfig } from "../daemon/handlers/config.js";
|
|
70
77
|
import type { HandlerContext } from "../daemon/handlers/shared.js";
|
|
71
78
|
import type {
|
|
@@ -121,6 +128,7 @@ describe("Ingress reconcile trigger in handleIngressConfig", () => {
|
|
|
121
128
|
beforeEach(() => {
|
|
122
129
|
rawConfigStore = {};
|
|
123
130
|
httpTokenValue = null;
|
|
131
|
+
mintedToken = null;
|
|
124
132
|
reconcileCalls = [];
|
|
125
133
|
fetchShouldFail = false;
|
|
126
134
|
|
|
@@ -186,7 +194,7 @@ describe("Ingress reconcile trigger in handleIngressConfig", () => {
|
|
|
186
194
|
|
|
187
195
|
// ── Token present/missing behavior ──────────────────────────────────────
|
|
188
196
|
|
|
189
|
-
test("
|
|
197
|
+
test("always triggers reconcile even when readHttpToken returns null (uses mintDaemonDeliveryToken)", async () => {
|
|
190
198
|
httpTokenValue = null;
|
|
191
199
|
|
|
192
200
|
const msg: IngressConfigRequest = {
|
|
@@ -206,12 +214,12 @@ describe("Ingress reconcile trigger in handleIngressConfig", () => {
|
|
|
206
214
|
const res = sent[0] as { type: string; success: boolean };
|
|
207
215
|
expect(res.success).toBe(true);
|
|
208
216
|
|
|
209
|
-
//
|
|
210
|
-
expect(reconcileCalls).toHaveLength(
|
|
217
|
+
// Reconcile is always triggered using mintDaemonDeliveryToken
|
|
218
|
+
expect(reconcileCalls).toHaveLength(1);
|
|
211
219
|
});
|
|
212
220
|
|
|
213
|
-
test("triggers reconcile
|
|
214
|
-
|
|
221
|
+
test("triggers reconcile with mintDaemonDeliveryToken bearer token", async () => {
|
|
222
|
+
mintedToken = "test-bearer-token";
|
|
215
223
|
|
|
216
224
|
const msg: IngressConfigRequest = {
|
|
217
225
|
type: "ingress_config",
|
|
@@ -144,7 +144,7 @@ describe("vellum mcp list", () => {
|
|
|
144
144
|
expect(stdout).toContain("stdio");
|
|
145
145
|
expect(stdout).toContain("npx -y some-mcp-server");
|
|
146
146
|
expect(stdout).toContain("low");
|
|
147
|
-
});
|
|
147
|
+
}, 15_000);
|
|
148
148
|
|
|
149
149
|
test("--json outputs valid JSON", () => {
|
|
150
150
|
writeConfig({
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import * as net from "node:net";
|
|
2
2
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const actualEnv = await import("../config/env.js");
|
|
5
|
+
mock.module("../config/env.js", () => ({
|
|
6
|
+
...actualEnv,
|
|
7
|
+
isHttpAuthDisabled: () => true,
|
|
8
|
+
isMonitoringEnabled: () => false,
|
|
9
|
+
}));
|
|
5
10
|
|
|
6
11
|
// ─── Mocks (must be before any imports that depend on them) ─────────────────
|
|
7
12
|
|
|
@@ -351,7 +356,9 @@ mock.module("../subagent/index.js", () => ({
|
|
|
351
356
|
|
|
352
357
|
// ── Mock IPC protocol helpers ──────────────────────────────────────────────
|
|
353
358
|
|
|
359
|
+
const actualIpcProtocol = await import("../daemon/ipc-protocol.js");
|
|
354
360
|
mock.module("../daemon/ipc-protocol.js", () => ({
|
|
361
|
+
...actualIpcProtocol,
|
|
355
362
|
normalizeThreadType: (t: string) => t ?? "primary",
|
|
356
363
|
}));
|
|
357
364
|
|
|
@@ -414,6 +421,7 @@ function createCtx(overrides?: Partial<HandlerContext>): {
|
|
|
414
421
|
setAssistantId: noop,
|
|
415
422
|
setChannelCapabilities: noop,
|
|
416
423
|
setGuardianContext: noop,
|
|
424
|
+
setAuthContext: noop,
|
|
417
425
|
setCommandIntent: noop,
|
|
418
426
|
updateClient: noop,
|
|
419
427
|
processMessage: async () => {},
|
|
@@ -108,6 +108,7 @@ function makeCompletingSession(): Session {
|
|
|
108
108
|
setChannelCapabilities: () => {},
|
|
109
109
|
setAssistantId: () => {},
|
|
110
110
|
setGuardianContext: () => {},
|
|
111
|
+
setAuthContext: () => {},
|
|
111
112
|
setCommandIntent: () => {},
|
|
112
113
|
setTurnChannelContext: () => {},
|
|
113
114
|
setTurnInterfaceContext: () => {},
|
|
@@ -160,6 +161,7 @@ function makeHangingSession(): Session {
|
|
|
160
161
|
setChannelCapabilities: () => {},
|
|
161
162
|
setAssistantId: () => {},
|
|
162
163
|
setGuardianContext: () => {},
|
|
164
|
+
setAuthContext: () => {},
|
|
163
165
|
setCommandIntent: () => {},
|
|
164
166
|
setTurnChannelContext: () => {},
|
|
165
167
|
setTurnInterfaceContext: () => {},
|
|
@@ -236,7 +238,11 @@ function makePendingApprovalSession(
|
|
|
236
238
|
},
|
|
237
239
|
setChannelCapabilities: () => {},
|
|
238
240
|
setAssistantId: () => {},
|
|
239
|
-
|
|
241
|
+
guardianContext: undefined as unknown,
|
|
242
|
+
setGuardianContext(this: { guardianContext: unknown }, ctx: unknown) {
|
|
243
|
+
this.guardianContext = ctx;
|
|
244
|
+
},
|
|
245
|
+
setAuthContext: () => {},
|
|
240
246
|
setCommandIntent: () => {},
|
|
241
247
|
setTurnChannelContext: () => {},
|
|
242
248
|
setTurnInterfaceContext: () => {},
|
|
@@ -290,7 +296,7 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
290
296
|
createBinding({
|
|
291
297
|
assistantId: "self",
|
|
292
298
|
channel: "vellum",
|
|
293
|
-
guardianExternalUserId: "
|
|
299
|
+
guardianExternalUserId: "dev-bypass",
|
|
294
300
|
guardianDeliveryChatId: "vellum",
|
|
295
301
|
guardianPrincipalId: "test-principal-id",
|
|
296
302
|
});
|
|
@@ -37,6 +37,10 @@ mock.module("../util/platform.js", () => ({
|
|
|
37
37
|
readHttpToken: () => "runtime-token",
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
+
mock.module("../runtime/auth/token-service.js", () => ({
|
|
41
|
+
mintDaemonDeliveryToken: () => "runtime-token",
|
|
42
|
+
}));
|
|
43
|
+
|
|
40
44
|
mock.module("../config/loader.js", () => ({
|
|
41
45
|
loadConfig: () => configState,
|
|
42
46
|
}));
|
|
@@ -72,6 +72,7 @@ const {
|
|
|
72
72
|
ensurePromptFiles,
|
|
73
73
|
stripCommentLines,
|
|
74
74
|
buildExternalCommsIdentitySection,
|
|
75
|
+
buildPhoneCallsRoutingSection,
|
|
75
76
|
} = await import("../config/system-prompt.js");
|
|
76
77
|
|
|
77
78
|
/** Strip the Configuration and Skills sections so base-prompt tests stay focused. */
|
|
@@ -224,8 +225,23 @@ describe("buildSystemPrompt", () => {
|
|
|
224
225
|
expect(section).toContain("Occasional variations are acceptable");
|
|
225
226
|
});
|
|
226
227
|
|
|
227
|
-
test("includes
|
|
228
|
-
const result = buildSystemPrompt(
|
|
228
|
+
test("includes phone calls routing section", () => {
|
|
229
|
+
const result = buildSystemPrompt();
|
|
230
|
+
expect(result).toContain("## Routing: Phone Calls");
|
|
231
|
+
expect(result).toContain('skill: "phone-calls"');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("buildPhoneCallsRoutingSection returns section with expected content", () => {
|
|
235
|
+
const section = buildPhoneCallsRoutingSection();
|
|
236
|
+
expect(section).toContain("## Routing: Phone Calls");
|
|
237
|
+
expect(section).toContain("Trigger phrases");
|
|
238
|
+
expect(section).toContain("Exclusivity rules");
|
|
239
|
+
expect(section).toContain("phone-calls");
|
|
240
|
+
expect(section).toContain("Do NOT improvise Twilio setup instructions");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("includes memory persistence section", () => {
|
|
244
|
+
const result = buildSystemPrompt();
|
|
229
245
|
expect(result).toContain("## Memory Persistence");
|
|
230
246
|
expect(result).toContain("memory_save");
|
|
231
247
|
expect(result).toContain("Saved > unsaved. Always.");
|