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.
- package/bun.lock +4 -4
- package/package.json +4 -3
- package/src/__tests__/asset-materialize-tool.test.ts +2 -2
- package/src/__tests__/checker.test.ts +104 -0
- package/src/__tests__/config-schema.test.ts +0 -6
- package/src/__tests__/forbidden-legacy-symbols.test.ts +69 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +538 -0
- package/src/__tests__/ingress-url-consistency.test.ts +214 -0
- package/src/__tests__/ipc-snapshot.test.ts +17 -5
- package/src/__tests__/oauth-callback-registry.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +304 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +51 -12
- package/src/__tests__/public-ingress-urls.test.ts +222 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
- package/src/__tests__/runtime-events-sse.test.ts +162 -0
- package/src/__tests__/tool-executor.test.ts +88 -0
- package/src/__tests__/turn-commit.test.ts +64 -0
- package/src/__tests__/twilio-provider.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +4 -4
- package/src/__tests__/twitter-auth-handler.test.ts +87 -2
- package/src/calls/call-domain.ts +8 -6
- package/src/calls/twilio-config.ts +18 -3
- package/src/calls/twilio-routes.ts +10 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +25 -0
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
- package/src/config/defaults.ts +4 -1
- package/src/config/schema.ts +30 -6
- package/src/config/system-prompt.ts +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +5 -4
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -2
- package/src/config/vellum-skills/telegram-setup/SKILL.md +3 -3
- package/src/daemon/computer-use-session.ts +2 -1
- package/src/daemon/handlers/config.ts +49 -17
- package/src/daemon/handlers/sessions.ts +2 -2
- package/src/daemon/handlers/shared.ts +1 -0
- package/src/daemon/handlers/subagents.ts +85 -2
- package/src/daemon/handlers/twitter-auth.ts +31 -2
- package/src/daemon/handlers/work-items.ts +1 -1
- package/src/daemon/ipc-contract-inventory.json +8 -4
- package/src/daemon/ipc-contract.ts +34 -15
- package/src/daemon/lifecycle.ts +9 -4
- package/src/daemon/server.ts +7 -0
- package/src/daemon/session-tool-setup.ts +8 -1
- package/src/inbound/public-ingress-urls.ts +112 -0
- package/src/memory/attachments-store.ts +0 -1
- package/src/memory/channel-delivery-store.ts +0 -1
- package/src/memory/conversation-key-store.ts +0 -1
- package/src/memory/db.ts +472 -148
- package/src/memory/llm-usage-store.ts +0 -1
- package/src/memory/runs-store.ts +51 -6
- package/src/memory/schema.ts +2 -6
- package/src/runtime/gateway-client.ts +7 -1
- package/src/runtime/http-server.ts +174 -7
- package/src/runtime/routes/channel-routes.ts +7 -2
- package/src/runtime/routes/events-routes.ts +79 -0
- package/src/runtime/routes/run-routes.ts +43 -0
- package/src/runtime/run-orchestrator.ts +64 -7
- package/src/security/oauth-callback-registry.ts +66 -0
- package/src/security/oauth2.ts +208 -58
- package/src/subagent/manager.ts +3 -1
- package/src/swarm/backend-claude-code.ts +1 -1
- package/src/tools/assets/search.ts +1 -36
- package/src/tools/claude-code/claude-code.ts +3 -3
- package/src/tools/tasks/work-item-list.ts +16 -2
- package/src/tools/tasks/work-item-run.ts +78 -0
- package/src/util/platform.ts +1 -1
- package/src/work-items/work-item-runner.ts +171 -0
- package/src/workspace/provider-commit-message-generator.ts +39 -23
- package/src/workspace/turn-commit.ts +6 -2
- package/src/__tests__/handlers-twilio-config.test.ts +0 -221
- package/src/calls/__tests__/twilio-webhook-urls.test.ts +0 -162
- 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.
|
|
15
|
-
"@vellumai/vellum-gateway": "0.1.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
-
"@vellumai/vellum-gateway": "0.1.
|
|
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,
|
|
253
|
-
VALUES ('${fakeId}', '
|
|
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
|
+
});
|