vellum 0.2.7 → 0.2.9

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 (76) hide show
  1. package/bun.lock +4 -4
  2. package/package.json +4 -3
  3. package/src/__tests__/asset-materialize-tool.test.ts +2 -2
  4. package/src/__tests__/checker.test.ts +104 -0
  5. package/src/__tests__/config-schema.test.ts +0 -6
  6. package/src/__tests__/forbidden-legacy-symbols.test.ts +69 -0
  7. package/src/__tests__/gateway-only-enforcement.test.ts +538 -0
  8. package/src/__tests__/ingress-url-consistency.test.ts +214 -0
  9. package/src/__tests__/ipc-snapshot.test.ts +17 -5
  10. package/src/__tests__/oauth-callback-registry.test.ts +85 -0
  11. package/src/__tests__/oauth2-gateway-transport.test.ts +304 -0
  12. package/src/__tests__/provider-commit-message-generator.test.ts +51 -12
  13. package/src/__tests__/public-ingress-urls.test.ts +222 -0
  14. package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
  15. package/src/__tests__/runtime-events-sse.test.ts +162 -0
  16. package/src/__tests__/tool-executor.test.ts +88 -0
  17. package/src/__tests__/turn-commit.test.ts +64 -0
  18. package/src/__tests__/twilio-provider.test.ts +1 -1
  19. package/src/__tests__/twilio-routes.test.ts +4 -4
  20. package/src/__tests__/twitter-auth-handler.test.ts +87 -2
  21. package/src/calls/call-domain.ts +8 -6
  22. package/src/calls/twilio-config.ts +18 -3
  23. package/src/calls/twilio-routes.ts +10 -2
  24. package/src/config/bundled-skills/tasks/TOOLS.json +25 -0
  25. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
  26. package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
  27. package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
  28. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
  29. package/src/config/defaults.ts +4 -1
  30. package/src/config/schema.ts +30 -6
  31. package/src/config/system-prompt.ts +1 -1
  32. package/src/config/types.ts +1 -0
  33. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +5 -4
  34. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -2
  35. package/src/config/vellum-skills/telegram-setup/SKILL.md +3 -3
  36. package/src/daemon/computer-use-session.ts +2 -1
  37. package/src/daemon/handlers/config.ts +49 -17
  38. package/src/daemon/handlers/sessions.ts +2 -2
  39. package/src/daemon/handlers/shared.ts +1 -0
  40. package/src/daemon/handlers/subagents.ts +85 -2
  41. package/src/daemon/handlers/twitter-auth.ts +31 -2
  42. package/src/daemon/handlers/work-items.ts +1 -1
  43. package/src/daemon/ipc-contract-inventory.json +8 -4
  44. package/src/daemon/ipc-contract.ts +34 -15
  45. package/src/daemon/lifecycle.ts +9 -4
  46. package/src/daemon/server.ts +7 -0
  47. package/src/daemon/session-tool-setup.ts +8 -1
  48. package/src/inbound/public-ingress-urls.ts +112 -0
  49. package/src/memory/attachments-store.ts +0 -1
  50. package/src/memory/channel-delivery-store.ts +0 -1
  51. package/src/memory/conversation-key-store.ts +0 -1
  52. package/src/memory/db.ts +472 -148
  53. package/src/memory/llm-usage-store.ts +0 -1
  54. package/src/memory/runs-store.ts +51 -6
  55. package/src/memory/schema.ts +2 -6
  56. package/src/runtime/gateway-client.ts +7 -1
  57. package/src/runtime/http-server.ts +174 -7
  58. package/src/runtime/routes/channel-routes.ts +7 -2
  59. package/src/runtime/routes/events-routes.ts +79 -0
  60. package/src/runtime/routes/run-routes.ts +43 -0
  61. package/src/runtime/run-orchestrator.ts +64 -7
  62. package/src/security/oauth-callback-registry.ts +66 -0
  63. package/src/security/oauth2.ts +208 -58
  64. package/src/subagent/manager.ts +3 -1
  65. package/src/swarm/backend-claude-code.ts +1 -1
  66. package/src/tools/assets/search.ts +1 -36
  67. package/src/tools/claude-code/claude-code.ts +3 -3
  68. package/src/tools/tasks/work-item-list.ts +16 -2
  69. package/src/tools/tasks/work-item-run.ts +78 -0
  70. package/src/util/platform.ts +1 -1
  71. package/src/work-items/work-item-runner.ts +171 -0
  72. package/src/workspace/provider-commit-message-generator.ts +39 -23
  73. package/src/workspace/turn-commit.ts +6 -2
  74. package/src/__tests__/handlers-twilio-config.test.ts +0 -221
  75. package/src/calls/__tests__/twilio-webhook-urls.test.ts +0 -162
  76. package/src/calls/twilio-webhook-urls.ts +0 -50
package/bun.lock CHANGED
@@ -11,8 +11,8 @@
11
11
  "@huggingface/transformers": "^3.8.1",
12
12
  "@qdrant/js-client-rest": "^1.16.2",
13
13
  "@sentry/node": "^10.38.0",
14
- "@vellumai/cli": "0.1.8",
15
- "@vellumai/vellum-gateway": "0.1.9",
14
+ "@vellumai/cli": "0.1.9",
15
+ "@vellumai/vellum-gateway": "0.1.10",
16
16
  "agentmail": "^0.1.0",
17
17
  "archiver": "^7.0.1",
18
18
  "commander": "^13.1.0",
@@ -542,9 +542,9 @@
542
542
 
543
543
  "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.0", "", { "dependencies": { "@typescript-eslint/types": "8.56.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg=="],
544
544
 
545
- "@vellumai/cli": ["@vellumai/cli@0.1.8", "", { "dependencies": { "ink": "^6.7.0", "react": "^19.2.4" }, "bin": { "vellum-cli": "src/index.ts" } }, "sha512-36P6W1rV1dK9Qg0N3oFjffz1CSarkGBgqc2ZGEI1de9RPSkVWBI0QmKCZrPeg1/qDzEh3InUIsV1qjNIe7z1pg=="],
545
+ "@vellumai/cli": ["@vellumai/cli@0.1.9", "", { "dependencies": { "ink": "^6.7.0", "react": "^19.2.4" }, "bin": { "vellum-cli": "src/index.ts" } }, "sha512-R5f9e6K2w+k5AwicpM4lNaWnXWaD9MvCRv6YCS+c7KuMUx8yD/jzRzSJTX1cIRNLcyty1bHrpInOQ2Wl5JeZDw=="],
546
546
 
547
- "@vellumai/vellum-gateway": ["@vellumai/vellum-gateway@0.1.9", "", { "dependencies": { "file-type": "^21.3.0", "pino": "^9.6.0", "pino-pretty": "^13.1.3", "zod": "^4.3.6" } }, "sha512-LNE5ueTWvyi4jJxIb99qKyanOX2H9DftR2CliC4vfU6jjLQdvvD3vZccx/VS5uVhfQsIQK08HF1XF9MtCwoJHA=="],
547
+ "@vellumai/vellum-gateway": ["@vellumai/vellum-gateway@0.1.10", "", { "dependencies": { "file-type": "^21.3.0", "pino": "^9.6.0", "pino-pretty": "^13.1.3", "zod": "^4.3.6" } }, "sha512-a41fGexW8RpWL4RTfZ3EM+XJMvz7t26D1axu2xAtZioXW3ZWMLGuogHnIJsgglzESl49E6VmmUsUGeD+dseV2w=="],
548
548
 
549
549
  "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
550
550
 
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "vellum",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "vellum": "./src/index.ts"
7
7
  },
8
8
  "scripts": {
9
9
  "dev": "bun run src/index.ts",
10
+ "daemon:restart:http": "RUNTIME_HTTP_PORT=7821 bun run src/index.ts daemon restart",
10
11
  "db:generate": "drizzle-kit generate",
11
12
  "db:push": "drizzle-kit push",
12
13
  "ipc:inventory": "bun run scripts/ipc/check-contract-inventory.ts",
@@ -28,8 +29,8 @@
28
29
  "@huggingface/transformers": "^3.8.1",
29
30
  "@qdrant/js-client-rest": "^1.16.2",
30
31
  "@sentry/node": "^10.38.0",
31
- "@vellumai/cli": "0.1.8",
32
- "@vellumai/vellum-gateway": "0.1.9",
32
+ "@vellumai/cli": "0.1.9",
33
+ "@vellumai/vellum-gateway": "0.1.10",
33
34
  "agentmail": "^0.1.0",
34
35
  "archiver": "^7.0.1",
35
36
  "commander": "^13.1.0",
@@ -249,8 +249,8 @@ describe('AssetMaterializeTool size limit', () => {
249
249
  const db = getDb();
250
250
  const fakeId = 'oversized-attachment';
251
251
  db.run(
252
- `INSERT INTO attachments (id, assistant_id, original_filename, mime_type, size_bytes, kind, data_base64, created_at)
253
- VALUES ('${fakeId}', 'ast-1', 'huge.bin', 'application/octet-stream', ${51 * 1024 * 1024}, 'document', 'AAAA', ${Date.now()})`,
252
+ `INSERT INTO attachments (id, original_filename, mime_type, size_bytes, kind, data_base64, created_at)
253
+ VALUES ('${fakeId}', 'huge.bin', 'application/octet-stream', ${51 * 1024 * 1024}, 'document', 'AAAA', ${Date.now()})`,
254
254
  );
255
255
 
256
256
  const result = await assetMaterializeTool.execute(
@@ -3854,3 +3854,107 @@ describe('computer-use tool permission defaults', () => {
3854
3854
  expect(risk).toBe(RiskLevel.Low);
3855
3855
  });
3856
3856
  });
3857
+
3858
+ // ---------------------------------------------------------------------------
3859
+ // Scope-matching behavior: project-scoped vs everywhere rules
3860
+ // ---------------------------------------------------------------------------
3861
+
3862
+ describe('scope matching behavior', () => {
3863
+ beforeEach(() => {
3864
+ clearCache();
3865
+ testConfig.permissions = { mode: 'legacy' };
3866
+ try { rmSync(join(checkerTestDir, 'protected', 'trust.json')); } catch { /* may not exist */ }
3867
+ });
3868
+
3869
+ test('project-scoped rule matches tool invocations from within that directory', async () => {
3870
+ const projectDir = '/home/user/my-project';
3871
+ // Use the pattern format that file tools produce: "toolName:path/**"
3872
+ addRule('file_write', 'file_write:/home/user/my-project/**', projectDir);
3873
+
3874
+ // Invocation from within the project directory should match
3875
+ const result = await check('file_write', { path: '/home/user/my-project/src/index.ts' }, projectDir);
3876
+ expect(result.decision).toBe('allow');
3877
+ expect(result.matchedRule).toBeDefined();
3878
+ expect(result.matchedRule!.scope).toBe(projectDir);
3879
+ });
3880
+
3881
+ test('project-scoped rule matches tool invocations from subdirectory of project', async () => {
3882
+ const projectDir = '/home/user/my-project';
3883
+ addRule('file_write', 'file_write:/home/user/my-project/**', projectDir);
3884
+
3885
+ // Invocation from a subdirectory should also match (scope is a prefix match)
3886
+ const result = await check('file_write', { path: '/home/user/my-project/src/index.ts' }, '/home/user/my-project/src');
3887
+ expect(result.decision).toBe('allow');
3888
+ expect(result.matchedRule).toBeDefined();
3889
+ expect(result.matchedRule!.scope).toBe(projectDir);
3890
+ });
3891
+
3892
+ test('project-scoped rule does NOT match invocations from sibling directory', async () => {
3893
+ const projectDir = '/home/user/my-project';
3894
+ // Use a broad pattern that matches any file, scoped to the project
3895
+ addRule('file_write', 'file_write:*', projectDir);
3896
+
3897
+ // Invocation from a sibling directory should NOT match the project-scoped rule
3898
+ const result = await check('file_write', { path: '/home/user/other-project/file.ts' }, '/home/user/other-project');
3899
+ expect(result.decision).toBe('prompt');
3900
+ });
3901
+
3902
+ test('project-scoped rule does NOT match invocations from parent directory', async () => {
3903
+ const projectDir = '/home/user/my-project';
3904
+ addRule('file_write', 'file_write:*', projectDir);
3905
+
3906
+ // Invocation from a parent directory should NOT match
3907
+ const result = await check('file_write', { path: '/home/user/file.txt' }, '/home/user');
3908
+ expect(result.decision).toBe('prompt');
3909
+ });
3910
+
3911
+ test('project-scoped rule does NOT match directory with shared prefix', async () => {
3912
+ // A rule for /home/user/project should NOT match /home/user/project-evil
3913
+ // (directory-boundary enforcement in matchesScope)
3914
+ const projectDir = '/home/user/project';
3915
+ addRule('file_write', 'file_write:*', projectDir);
3916
+
3917
+ const result = await check('file_write', { path: '/home/user/project-evil/malicious.ts' }, '/home/user/project-evil');
3918
+ expect(result.decision).toBe('prompt');
3919
+ });
3920
+
3921
+ test('everywhere-scoped rule matches invocations from any directory', async () => {
3922
+ addRule('file_write', 'file_write:*', 'everywhere');
3923
+
3924
+ // Should match from various directories
3925
+ const r1 = await check('file_write', { path: 'file.ts' }, '/home/user/project-a');
3926
+ expect(r1.decision).toBe('allow');
3927
+ expect(r1.matchedRule).toBeDefined();
3928
+ expect(r1.matchedRule!.scope).toBe('everywhere');
3929
+
3930
+ const r2 = await check('file_write', { path: 'output.txt' }, '/var/tmp');
3931
+ expect(r2.decision).toBe('allow');
3932
+ expect(r2.matchedRule!.scope).toBe('everywhere');
3933
+
3934
+ const r3 = await check('file_write', { path: 'file.json' }, '/opt/data');
3935
+ expect(r3.decision).toBe('allow');
3936
+ expect(r3.matchedRule!.scope).toBe('everywhere');
3937
+ });
3938
+
3939
+ test('bash rule scoped to project matches commands within that project', async () => {
3940
+ const projectDir = '/home/user/my-project';
3941
+ addRule('bash', 'npm *', projectDir);
3942
+
3943
+ const result = await check('bash', { command: 'npm install' }, projectDir);
3944
+ expect(result.decision).toBe('allow');
3945
+ expect(result.matchedRule).toBeDefined();
3946
+ });
3947
+
3948
+ test('bash rule scoped to project does NOT match commands from different project', async () => {
3949
+ const projectDir = '/home/user/my-project';
3950
+ addRule('bash', 'npm *', projectDir);
3951
+
3952
+ const result = await check('bash', { command: 'npm install' }, '/home/user/other-project');
3953
+ // npm install is Low risk, so it falls through to auto-allow via the
3954
+ // default sandbox bash rule, not via the project-scoped rule.
3955
+ // The key assertion is that the project-scoped rule is NOT the matched rule.
3956
+ if (result.matchedRule) {
3957
+ expect(result.matchedRule.scope).not.toBe(projectDir);
3958
+ }
3959
+ });
3960
+ });
@@ -646,7 +646,6 @@ describe('AssistantConfigSchema', () => {
646
646
  expect(result.calls).toEqual({
647
647
  enabled: true,
648
648
  provider: 'twilio',
649
- webhookBaseUrl: '',
650
649
  maxDurationSeconds: 3600,
651
650
  userConsultTimeoutSeconds: 120,
652
651
  disclosure: {
@@ -659,11 +658,6 @@ describe('AssistantConfigSchema', () => {
659
658
  });
660
659
  });
661
660
 
662
- test('calls.webhookBaseUrl defaults to empty string', () => {
663
- const result = AssistantConfigSchema.parse({});
664
- expect(result.calls.webhookBaseUrl).toBe('');
665
- });
666
-
667
661
  test('accepts valid calls config overrides', () => {
668
662
  const result = AssistantConfigSchema.parse({
669
663
  calls: {
@@ -0,0 +1,69 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import { execSync } from 'node:child_process';
3
+ import { resolve } from 'node:path';
4
+
5
+ /**
6
+ * Guard test: fail if any legacy Twilio ingress symbols reappear in
7
+ * production source code, docs, configs, or scripts.
8
+ *
9
+ * Context: As part of the gateway-only ingress migration (#5948, #6000),
10
+ * all Twilio webhook configuration was consolidated into the gateway service.
11
+ * The assistant no longer manages its own Twilio webhook URLs — the gateway
12
+ * is the single ingress point for all telephony webhooks. Re-introducing
13
+ * these symbols in the assistant would bypass that architecture and create
14
+ * a split-brain ingress problem.
15
+ *
16
+ * Forbidden symbols:
17
+ * - legacy uppercase Twilio webhook base env var
18
+ * - twilioWebhookBaseUrl
19
+ * - twilio_webhook_config
20
+ * - calls.webhookBaseUrl
21
+ *
22
+ * Excluded directories:
23
+ * - node_modules — third-party code, not under our control
24
+ * - __tests__ — test files (including this guard test) reference the
25
+ * symbols in grep patterns and assertions
26
+ * - .private — local-only developer notes and scratch files
27
+ */
28
+ describe('forbidden legacy symbols', () => {
29
+ test('no production code references removed Twilio ingress symbols', () => {
30
+ const legacyEnvVar = ['TWILIO', 'WEBHOOK', 'BASE', 'URL'].join('_');
31
+ const forbiddenSymbols = [
32
+ legacyEnvVar,
33
+ 'twilioWebhookBaseUrl',
34
+ 'twilio_webhook_config',
35
+ 'calls.webhookBaseUrl',
36
+ ];
37
+ const escapedPattern = forbiddenSymbols
38
+ .map((symbol) => symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
39
+ .join('|');
40
+
41
+ const repoRoot = resolve(__dirname, '..', '..', '..');
42
+ let matches = '';
43
+ try {
44
+ matches = execSync(
45
+ `grep -rn -E "${escapedPattern}"` +
46
+ ' --include="*.ts" --include="*.tsx" --include="*.js" --include="*.mjs" --include="*.swift"' +
47
+ ' --include="*.json" --include="*.md" --include="*.yml" --include="*.yaml"' +
48
+ ' --include="*.sh"' +
49
+ ' --exclude-dir=node_modules --exclude-dir=__tests__ --exclude-dir=.private' +
50
+ ' .',
51
+ { cwd: repoRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
52
+ );
53
+ } catch (err: unknown) {
54
+ // grep exits with code 1 when no matches are found — that is the expected (passing) case
55
+ const exitCode = (err as { status?: number }).status;
56
+ if (exitCode === 1) {
57
+ // No matches found — test passes
58
+ return;
59
+ }
60
+ // Any other error is unexpected
61
+ throw err;
62
+ }
63
+
64
+ // If we reach here, grep found matches (exit code 0) — fail the test
65
+ expect(matches.trim()).toBe(
66
+ '', // should be empty — if not, the matched lines appear in the failure message
67
+ );
68
+ });
69
+ });