vellum 0.2.13 → 0.2.14

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 (207) hide show
  1. package/README.md +32 -0
  2. package/bun.lock +2 -2
  3. package/docs/skills.md +4 -4
  4. package/package.json +2 -2
  5. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
  6. package/src/__tests__/app-git-history.test.ts +176 -0
  7. package/src/__tests__/app-git-service.test.ts +169 -0
  8. package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
  9. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
  10. package/src/__tests__/browser-skill-endstate.test.ts +6 -6
  11. package/src/__tests__/call-bridge.test.ts +105 -13
  12. package/src/__tests__/call-domain.test.ts +163 -0
  13. package/src/__tests__/call-orchestrator.test.ts +113 -0
  14. package/src/__tests__/call-routes-http.test.ts +246 -6
  15. package/src/__tests__/channel-approval-routes.test.ts +438 -0
  16. package/src/__tests__/channel-approval.test.ts +266 -0
  17. package/src/__tests__/channel-approvals.test.ts +393 -0
  18. package/src/__tests__/channel-delivery-store.test.ts +447 -0
  19. package/src/__tests__/checker.test.ts +607 -1048
  20. package/src/__tests__/cli.test.ts +1 -56
  21. package/src/__tests__/config-schema.test.ts +137 -18
  22. package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
  23. package/src/__tests__/conflict-policy.test.ts +121 -0
  24. package/src/__tests__/conflict-store.test.ts +2 -0
  25. package/src/__tests__/contacts-tools.test.ts +3 -3
  26. package/src/__tests__/contradiction-checker.test.ts +99 -1
  27. package/src/__tests__/credential-security-invariants.test.ts +22 -6
  28. package/src/__tests__/credential-vault-unit.test.ts +780 -0
  29. package/src/__tests__/elevenlabs-client.test.ts +62 -0
  30. package/src/__tests__/ephemeral-permissions.test.ts +73 -23
  31. package/src/__tests__/filesystem-tools.test.ts +579 -0
  32. package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
  33. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
  34. package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
  35. package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
  36. package/src/__tests__/handlers-slack-config.test.ts +2 -1
  37. package/src/__tests__/handlers-telegram-config.test.ts +855 -0
  38. package/src/__tests__/handlers-twitter-config.test.ts +141 -1
  39. package/src/__tests__/hooks-runner.test.ts +6 -2
  40. package/src/__tests__/host-file-edit-tool.test.ts +124 -0
  41. package/src/__tests__/host-file-read-tool.test.ts +62 -0
  42. package/src/__tests__/host-file-write-tool.test.ts +59 -0
  43. package/src/__tests__/host-shell-tool.test.ts +251 -0
  44. package/src/__tests__/ingress-reconcile.test.ts +581 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +100 -41
  46. package/src/__tests__/ipc-validate.test.ts +50 -0
  47. package/src/__tests__/key-migration.test.ts +23 -0
  48. package/src/__tests__/memory-regressions.test.ts +99 -0
  49. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  50. package/src/__tests__/oauth-callback-registry.test.ts +11 -4
  51. package/src/__tests__/playbook-execution.test.ts +502 -0
  52. package/src/__tests__/playbook-tools.test.ts +4 -6
  53. package/src/__tests__/public-ingress-urls.test.ts +34 -0
  54. package/src/__tests__/qdrant-manager.test.ts +267 -0
  55. package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
  56. package/src/__tests__/recurrence-engine.test.ts +9 -0
  57. package/src/__tests__/recurrence-types.test.ts +8 -0
  58. package/src/__tests__/registry.test.ts +1 -1
  59. package/src/__tests__/runtime-runs.test.ts +1 -25
  60. package/src/__tests__/schedule-store.test.ts +16 -14
  61. package/src/__tests__/schedule-tools.test.ts +83 -0
  62. package/src/__tests__/scheduler-recurrence.test.ts +111 -10
  63. package/src/__tests__/secret-allowlist.test.ts +18 -17
  64. package/src/__tests__/secret-ingress-handler.test.ts +11 -0
  65. package/src/__tests__/secret-scanner.test.ts +43 -0
  66. package/src/__tests__/session-conflict-gate.test.ts +442 -6
  67. package/src/__tests__/session-init.benchmark.test.ts +3 -0
  68. package/src/__tests__/session-process-bridge.test.ts +242 -0
  69. package/src/__tests__/session-skill-tools.test.ts +1 -1
  70. package/src/__tests__/shell-identity.test.ts +256 -0
  71. package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
  72. package/src/__tests__/subagent-tools.test.ts +637 -54
  73. package/src/__tests__/task-management-tools.test.ts +936 -0
  74. package/src/__tests__/task-runner.test.ts +2 -2
  75. package/src/__tests__/terminal-tools.test.ts +840 -0
  76. package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
  77. package/src/__tests__/tool-executor.test.ts +85 -151
  78. package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
  79. package/src/__tests__/trust-store.test.ts +27 -453
  80. package/src/__tests__/twilio-provider.test.ts +153 -3
  81. package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
  82. package/src/__tests__/twilio-routes-twiml.test.ts +4 -4
  83. package/src/__tests__/twilio-routes.test.ts +17 -262
  84. package/src/__tests__/twitter-auth-handler.test.ts +2 -1
  85. package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
  86. package/src/__tests__/twitter-cli-routing.test.ts +252 -0
  87. package/src/__tests__/twitter-oauth-client.test.ts +209 -0
  88. package/src/__tests__/workspace-policy.test.ts +213 -0
  89. package/src/calls/call-bridge.ts +92 -19
  90. package/src/calls/call-domain.ts +157 -5
  91. package/src/calls/call-orchestrator.ts +93 -7
  92. package/src/calls/call-store.ts +6 -0
  93. package/src/calls/elevenlabs-client.ts +8 -0
  94. package/src/calls/elevenlabs-config.ts +7 -5
  95. package/src/calls/twilio-provider.ts +91 -0
  96. package/src/calls/twilio-routes.ts +32 -37
  97. package/src/calls/types.ts +3 -1
  98. package/src/calls/voice-quality.ts +29 -7
  99. package/src/cli/twitter.ts +200 -21
  100. package/src/cli.ts +1 -20
  101. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
  102. package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
  103. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
  104. package/src/config/bundled-skills/messaging/SKILL.md +17 -2
  105. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
  106. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  107. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
  108. package/src/config/bundled-skills/phone-calls/SKILL.md +142 -34
  109. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
  110. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
  111. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
  112. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
  113. package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
  114. package/src/config/bundled-skills/twitter/SKILL.md +103 -17
  115. package/src/config/defaults.ts +10 -4
  116. package/src/config/schema.ts +80 -21
  117. package/src/config/types.ts +1 -0
  118. package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
  119. package/src/daemon/assistant-attachments.ts +4 -2
  120. package/src/daemon/handlers/apps.ts +69 -0
  121. package/src/daemon/handlers/config.ts +543 -24
  122. package/src/daemon/handlers/index.ts +1 -0
  123. package/src/daemon/handlers/sessions.ts +22 -6
  124. package/src/daemon/handlers/shared.ts +2 -1
  125. package/src/daemon/handlers/skills.ts +5 -20
  126. package/src/daemon/ipc-contract-inventory.json +28 -0
  127. package/src/daemon/ipc-contract.ts +168 -10
  128. package/src/daemon/ipc-validate.ts +17 -0
  129. package/src/daemon/lifecycle.ts +2 -0
  130. package/src/daemon/server.ts +78 -72
  131. package/src/daemon/session-attachments.ts +1 -1
  132. package/src/daemon/session-conflict-gate.ts +62 -6
  133. package/src/daemon/session-notifiers.ts +1 -1
  134. package/src/daemon/session-process.ts +62 -3
  135. package/src/daemon/session-tool-setup.ts +1 -2
  136. package/src/daemon/tls-certs.ts +189 -0
  137. package/src/daemon/video-thumbnail.ts +5 -3
  138. package/src/hooks/manager.ts +5 -9
  139. package/src/memory/app-git-service.ts +295 -0
  140. package/src/memory/app-store.ts +21 -0
  141. package/src/memory/conflict-intent.ts +47 -4
  142. package/src/memory/conflict-policy.ts +73 -0
  143. package/src/memory/conflict-store.ts +9 -1
  144. package/src/memory/contradiction-checker.ts +28 -0
  145. package/src/memory/conversation-key-store.ts +15 -0
  146. package/src/memory/db.ts +81 -0
  147. package/src/memory/embedding-local.ts +3 -13
  148. package/src/memory/external-conversation-store.ts +234 -0
  149. package/src/memory/job-handlers/conflict.ts +22 -2
  150. package/src/memory/jobs-worker.ts +67 -28
  151. package/src/memory/runs-store.ts +54 -7
  152. package/src/memory/schema.ts +20 -0
  153. package/src/messaging/provider.ts +9 -0
  154. package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
  155. package/src/messaging/providers/telegram-bot/client.ts +104 -0
  156. package/src/messaging/providers/telegram-bot/types.ts +15 -0
  157. package/src/messaging/registry.ts +1 -0
  158. package/src/permissions/checker.ts +48 -44
  159. package/src/permissions/prompter.ts +0 -4
  160. package/src/permissions/shell-identity.ts +227 -0
  161. package/src/permissions/trust-store.ts +76 -53
  162. package/src/permissions/types.ts +0 -19
  163. package/src/permissions/workspace-policy.ts +114 -0
  164. package/src/providers/retry.ts +12 -37
  165. package/src/runtime/assistant-event-hub.ts +41 -4
  166. package/src/runtime/channel-approval-parser.ts +60 -0
  167. package/src/runtime/channel-approval-types.ts +71 -0
  168. package/src/runtime/channel-approvals.ts +145 -0
  169. package/src/runtime/gateway-client.ts +16 -0
  170. package/src/runtime/http-server.ts +29 -9
  171. package/src/runtime/routes/call-routes.ts +52 -2
  172. package/src/runtime/routes/channel-routes.ts +296 -16
  173. package/src/runtime/routes/events-routes.ts +97 -28
  174. package/src/runtime/routes/run-routes.ts +2 -7
  175. package/src/runtime/run-orchestrator.ts +0 -3
  176. package/src/schedule/recurrence-engine.ts +26 -2
  177. package/src/schedule/recurrence-types.ts +1 -1
  178. package/src/schedule/schedule-store.ts +12 -3
  179. package/src/security/secret-scanner.ts +7 -0
  180. package/src/tasks/ephemeral-permissions.ts +0 -2
  181. package/src/tasks/task-scheduler.ts +2 -1
  182. package/src/tools/calls/call-start.ts +8 -0
  183. package/src/tools/execution-target.ts +21 -0
  184. package/src/tools/execution-timeout.ts +49 -0
  185. package/src/tools/executor.ts +6 -135
  186. package/src/tools/network/web-search.ts +9 -32
  187. package/src/tools/policy-context.ts +29 -0
  188. package/src/tools/schedule/update.ts +8 -1
  189. package/src/tools/terminal/parser.ts +16 -18
  190. package/src/tools/types.ts +4 -11
  191. package/src/twitter/oauth-client.ts +102 -0
  192. package/src/twitter/router.ts +101 -0
  193. package/src/util/debounce.ts +88 -0
  194. package/src/util/network-info.ts +47 -0
  195. package/src/util/platform.ts +29 -4
  196. package/src/util/promise-guard.ts +37 -0
  197. package/src/util/retry.ts +98 -0
  198. package/src/util/truncate.ts +1 -1
  199. package/src/workspace/git-service.ts +129 -112
  200. package/src/tools/contacts/contact-merge.ts +0 -55
  201. package/src/tools/contacts/contact-search.ts +0 -58
  202. package/src/tools/contacts/contact-upsert.ts +0 -64
  203. package/src/tools/playbooks/index.ts +0 -4
  204. package/src/tools/playbooks/playbook-create.ts +0 -96
  205. package/src/tools/playbooks/playbook-delete.ts +0 -52
  206. package/src/tools/playbooks/playbook-list.ts +0 -74
  207. package/src/tools/playbooks/playbook-update.ts +0 -111
@@ -189,6 +189,68 @@ describe('ElevenLabsClient', () => {
189
189
  }
190
190
  });
191
191
 
192
+ test('valid TwiML with XML declaration passes validation', async () => {
193
+ const twiml = '<?xml version="1.0"?><Response><Connect><Stream url="wss://el.io/stream"/></Connect></Response>';
194
+
195
+ globalThis.fetch = mock(async () =>
196
+ new Response(twiml, { status: 200 }),
197
+ ) as unknown as typeof globalThis.fetch;
198
+
199
+ const client = new ElevenLabsClient(DEFAULT_OPTIONS);
200
+ const result = await client.registerCall(DEFAULT_REQUEST);
201
+
202
+ expect(result.twiml).toBe(twiml);
203
+ });
204
+
205
+ test('valid TwiML with Response tag but no XML declaration passes validation', async () => {
206
+ const twiml = '<Response><Connect><Stream url="wss://el.io/stream"/></Connect></Response>';
207
+
208
+ globalThis.fetch = mock(async () =>
209
+ new Response(twiml, { status: 200 }),
210
+ ) as unknown as typeof globalThis.fetch;
211
+
212
+ const client = new ElevenLabsClient(DEFAULT_OPTIONS);
213
+ const result = await client.registerCall(DEFAULT_REQUEST);
214
+
215
+ expect(result.twiml).toBe(twiml);
216
+ });
217
+
218
+ test('non-XML response throws ELEVENLABS_INVALID_RESPONSE', async () => {
219
+ globalThis.fetch = mock(async () =>
220
+ new Response('{"error": "invalid"}', { status: 200 }),
221
+ ) as unknown as typeof globalThis.fetch;
222
+
223
+ const client = new ElevenLabsClient(DEFAULT_OPTIONS);
224
+
225
+ try {
226
+ await client.registerCall(DEFAULT_REQUEST);
227
+ expect(true).toBe(false); // Should not reach here
228
+ } catch (err) {
229
+ expect(err).toBeInstanceOf(ElevenLabsError);
230
+ const elErr = err as ElevenLabsError;
231
+ expect(elErr.code).toBe('ELEVENLABS_INVALID_RESPONSE');
232
+ expect(elErr.message).toContain('not valid TwiML/XML');
233
+ }
234
+ });
235
+
236
+ test('plain text response throws ELEVENLABS_INVALID_RESPONSE', async () => {
237
+ globalThis.fetch = mock(async () =>
238
+ new Response('some random text', { status: 200 }),
239
+ ) as unknown as typeof globalThis.fetch;
240
+
241
+ const client = new ElevenLabsClient(DEFAULT_OPTIONS);
242
+
243
+ try {
244
+ await client.registerCall(DEFAULT_REQUEST);
245
+ expect(true).toBe(false); // Should not reach here
246
+ } catch (err) {
247
+ expect(err).toBeInstanceOf(ElevenLabsError);
248
+ const elErr = err as ElevenLabsError;
249
+ expect(elErr.code).toBe('ELEVENLABS_INVALID_RESPONSE');
250
+ expect(elErr.message).toContain('not valid TwiML/XML');
251
+ }
252
+ });
253
+
192
254
  test('API key is not included in logged data', async () => {
193
255
  // The ElevenLabsClient logs agent_id and direction, but should never log the API key.
194
256
  // We verify this by checking the request structure, not the log output.
@@ -1,4 +1,4 @@
1
- import { describe, test, expect, beforeAll, beforeEach, mock } from 'bun:test';
1
+ import { describe, test, expect, beforeAll, beforeEach, afterEach, mock } from 'bun:test';
2
2
  import { mkdtempSync, mkdirSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
@@ -28,7 +28,7 @@ mock.module('../util/logger.js', () => ({
28
28
 
29
29
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
30
  const testConfig: Record<string, any> = {
31
- permissions: { mode: 'legacy' as 'legacy' | 'strict' },
31
+ permissions: { mode: 'legacy' as 'legacy' | 'strict' | 'workspace' },
32
32
  skills: { load: { extraDirs: [] as string[] } },
33
33
  sandbox: { enabled: false },
34
34
  };
@@ -73,15 +73,14 @@ describe('ephemeral-permissions', () => {
73
73
  expect(fileReadRule.id).toBe('ephemeral:run-123:file_read');
74
74
  expect(fileReadRule.tool).toBe('file_read');
75
75
  expect(fileReadRule.pattern).toBe('**');
76
- expect(fileReadRule.scope).toBe('/home/user/project');
76
+ expect(fileReadRule.scope).toBe('everywhere');
77
77
  expect(fileReadRule.decision).toBe('allow');
78
- expect(fileReadRule.priority).toBe(50);
79
- expect(fileReadRule.principalKind).toBe('task');
80
- expect(fileReadRule.principalId).toBe('run-123');
78
+ expect(fileReadRule.priority).toBe(75);
81
79
  expect(fileReadRule.createdAt).toBeGreaterThan(0);
82
80
 
83
- // Verify allowHighRisk is NOT set
84
- expect(fileReadRule.allowHighRisk).toBeUndefined();
81
+ // allowHighRisk is set because task runs execute asynchronously
82
+ // without interactive confirmation — pre-approved via preflight
83
+ expect(fileReadRule.allowHighRisk).toBe(true);
85
84
 
86
85
  // Check other rules have correct tool names
87
86
  expect(rules[1].tool).toBe('bash');
@@ -155,12 +154,9 @@ describe('ephemeral-permissions', () => {
155
154
  decision: 'allow',
156
155
  priority: 50,
157
156
  createdAt: Date.now(),
158
- principalKind: 'task',
159
- principalId: 'run-1',
160
157
  }];
161
158
 
162
159
  const ctx: PolicyContext = {
163
- principal: { kind: 'task', id: 'run-1' },
164
160
  ephemeralRules,
165
161
  };
166
162
 
@@ -188,12 +184,9 @@ describe('ephemeral-permissions', () => {
188
184
  decision: 'allow',
189
185
  priority: 50,
190
186
  createdAt: Date.now(),
191
- principalKind: 'task',
192
- principalId: 'run-1',
193
187
  }];
194
188
 
195
189
  const ctx: PolicyContext = {
196
- principal: { kind: 'task', id: 'run-1' },
197
190
  ephemeralRules,
198
191
  };
199
192
 
@@ -218,12 +211,9 @@ describe('ephemeral-permissions', () => {
218
211
  decision: 'allow',
219
212
  priority: 50,
220
213
  createdAt: Date.now(),
221
- principalKind: 'task',
222
- principalId: 'run-1',
223
214
  }];
224
215
 
225
216
  const ctx: PolicyContext = {
226
- principal: { kind: 'task', id: 'run-1' },
227
217
  ephemeralRules,
228
218
  };
229
219
 
@@ -258,12 +248,9 @@ describe('ephemeral-permissions', () => {
258
248
  decision: 'allow',
259
249
  priority: 50,
260
250
  createdAt: Date.now(),
261
- principalKind: 'task',
262
- principalId: 'run-1',
263
251
  }];
264
252
 
265
253
  const ctx: PolicyContext = {
266
- principal: { kind: 'task', id: 'run-1' },
267
254
  ephemeralRules,
268
255
  };
269
256
 
@@ -288,13 +275,10 @@ describe('ephemeral-permissions', () => {
288
275
  decision: 'allow',
289
276
  priority: 50,
290
277
  createdAt: Date.now(),
291
- principalKind: 'task',
292
- principalId: 'run-1',
293
278
  // Note: allowHighRisk is NOT set
294
279
  }];
295
280
 
296
281
  const ctx: PolicyContext = {
297
- principal: { kind: 'task', id: 'run-1' },
298
282
  ephemeralRules,
299
283
  };
300
284
 
@@ -309,4 +293,70 @@ describe('ephemeral-permissions', () => {
309
293
  expect(result.decision).toBe('prompt');
310
294
  });
311
295
  });
296
+
297
+ describe('workspace mode interactions', () => {
298
+ beforeEach(() => {
299
+ clearCache();
300
+ testConfig.permissions.mode = 'workspace';
301
+ });
302
+
303
+ afterEach(() => {
304
+ testConfig.permissions.mode = 'legacy';
305
+ });
306
+
307
+ test('workspace mode auto-allows workspace-scoped file_write (medium risk)', async () => {
308
+ const filePath = join(testDir, 'workspace-test-file.txt');
309
+ const result = await check('file_write', { path: filePath }, testDir);
310
+ expect(result.decision).toBe('allow');
311
+ expect(result.reason).toContain('Workspace mode');
312
+ });
313
+
314
+ test('workspace mode still prompts for file_write outside workspace', async () => {
315
+ const result = await check('file_write', { path: '/etc/config' }, testDir);
316
+ expect(result.decision).toBe('prompt');
317
+ });
318
+
319
+ test('explicit deny rule overrides workspace mode auto-allow', async () => {
320
+ addRule('file_write', '**', testDir, 'deny', 100);
321
+ const filePath = join(testDir, 'should-be-denied.txt');
322
+ const result = await check('file_write', { path: filePath }, testDir);
323
+ expect(result.decision).toBe('deny');
324
+ });
325
+
326
+ test('proxied bash still prompts in workspace mode', async () => {
327
+ const result = await check(
328
+ 'bash',
329
+ { command: 'echo hello', network_mode: 'proxied' },
330
+ testDir,
331
+ );
332
+ expect(result.decision).toBe('prompt');
333
+ expect(result.reason).toContain('Proxied');
334
+ });
335
+
336
+ test('ephemeral task rules + workspace mode: deny rule wins', async () => {
337
+ // Add a persistent deny rule for file_write in the workspace
338
+ addRule('file_write', '**', testDir, 'deny', 100);
339
+
340
+ // Create ephemeral allow rules (lower priority than deny)
341
+ const ephemeralRules: TrustRule[] = [{
342
+ id: 'ephemeral:run-ws:file_write',
343
+ tool: 'file_write',
344
+ pattern: '**',
345
+ scope: testDir,
346
+ decision: 'allow',
347
+ priority: 50,
348
+ createdAt: Date.now(),
349
+ }];
350
+
351
+ const ctx: PolicyContext = {
352
+ ephemeralRules,
353
+ };
354
+
355
+ const filePath = join(testDir, 'task-file.txt');
356
+ const result = await check('file_write', { path: filePath }, testDir, ctx);
357
+ // The persistent deny rule (priority 100) should override
358
+ // both the ephemeral allow (priority 50) and workspace mode auto-allow
359
+ expect(result.decision).toBe('deny');
360
+ });
361
+ });
312
362
  });