vellum 0.2.12 → 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.
- package/README.md +32 -0
- package/bun.lock +2 -2
- package/docs/skills.md +4 -4
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
- package/src/__tests__/browser-skill-endstate.test.ts +6 -6
- package/src/__tests__/call-bridge.test.ts +105 -13
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +171 -0
- package/src/__tests__/call-routes-http.test.ts +246 -6
- package/src/__tests__/channel-approval-routes.test.ts +438 -0
- package/src/__tests__/channel-approval.test.ts +266 -0
- package/src/__tests__/channel-approvals.test.ts +393 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/checker.test.ts +607 -1048
- package/src/__tests__/cli.test.ts +1 -56
- package/src/__tests__/config-schema.test.ts +402 -5
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +2 -0
- package/src/__tests__/contacts-tools.test.ts +3 -3
- package/src/__tests__/contradiction-checker.test.ts +99 -1
- package/src/__tests__/credential-security-invariants.test.ts +22 -6
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/elevenlabs-client.test.ts +271 -0
- package/src/__tests__/ephemeral-permissions.test.ts +73 -23
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
- package/src/__tests__/handlers-slack-config.test.ts +2 -1
- package/src/__tests__/handlers-telegram-config.test.ts +855 -0
- package/src/__tests__/handlers-twitter-config.test.ts +141 -1
- package/src/__tests__/hooks-runner.test.ts +6 -2
- package/src/__tests__/host-file-edit-tool.test.ts +124 -0
- package/src/__tests__/host-file-read-tool.test.ts +62 -0
- package/src/__tests__/host-file-write-tool.test.ts +59 -0
- package/src/__tests__/host-shell-tool.test.ts +251 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ipc-snapshot.test.ts +100 -41
- package/src/__tests__/ipc-validate.test.ts +50 -0
- package/src/__tests__/key-migration.test.ts +23 -0
- package/src/__tests__/memory-regressions.test.ts +99 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/oauth-callback-registry.test.ts +11 -4
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +4 -6
- package/src/__tests__/public-ingress-urls.test.ts +34 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
- package/src/__tests__/recurrence-engine.test.ts +9 -0
- package/src/__tests__/recurrence-types.test.ts +8 -0
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/runtime-runs.test.ts +1 -25
- package/src/__tests__/schedule-store.test.ts +16 -14
- package/src/__tests__/schedule-tools.test.ts +83 -0
- package/src/__tests__/scheduler-recurrence.test.ts +111 -10
- package/src/__tests__/secret-allowlist.test.ts +18 -17
- package/src/__tests__/secret-ingress-handler.test.ts +11 -0
- package/src/__tests__/secret-scanner.test.ts +43 -0
- package/src/__tests__/session-conflict-gate.test.ts +442 -6
- package/src/__tests__/session-init.benchmark.test.ts +3 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -1
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
- package/src/__tests__/subagent-tools.test.ts +637 -54
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +2 -2
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +85 -151
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/trust-store.test.ts +28 -453
- package/src/__tests__/twilio-provider.test.ts +153 -3
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +127 -0
- package/src/__tests__/twilio-routes.test.ts +17 -262
- package/src/__tests__/twitter-auth-handler.test.ts +2 -1
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/calls/call-bridge.ts +92 -19
- package/src/calls/call-domain.ts +157 -5
- package/src/calls/call-orchestrator.ts +96 -8
- package/src/calls/call-store.ts +6 -0
- package/src/calls/elevenlabs-client.ts +97 -0
- package/src/calls/elevenlabs-config.ts +31 -0
- package/src/calls/twilio-provider.ts +91 -0
- package/src/calls/twilio-routes.ts +50 -6
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-quality.ts +114 -0
- package/src/cli/twitter.ts +200 -21
- package/src/cli.ts +1 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
- package/src/config/bundled-skills/messaging/SKILL.md +17 -2
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +207 -19
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
- package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
- package/src/config/bundled-skills/twitter/SKILL.md +103 -17
- package/src/config/defaults.ts +26 -2
- package/src/config/schema.ts +178 -9
- package/src/config/types.ts +3 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
- package/src/daemon/assistant-attachments.ts +4 -2
- package/src/daemon/handlers/apps.ts +69 -0
- package/src/daemon/handlers/config.ts +543 -24
- package/src/daemon/handlers/index.ts +1 -0
- package/src/daemon/handlers/sessions.ts +22 -6
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +5 -20
- package/src/daemon/ipc-contract-inventory.json +28 -0
- package/src/daemon/ipc-contract.ts +168 -10
- package/src/daemon/ipc-validate.ts +17 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/server.ts +78 -72
- package/src/daemon/session-attachments.ts +1 -1
- package/src/daemon/session-conflict-gate.ts +62 -6
- package/src/daemon/session-notifiers.ts +1 -1
- package/src/daemon/session-process.ts +62 -3
- package/src/daemon/session-tool-setup.ts +1 -2
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/video-thumbnail.ts +5 -3
- package/src/hooks/manager.ts +5 -9
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +21 -0
- package/src/memory/conflict-intent.ts +47 -4
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +9 -1
- package/src/memory/contradiction-checker.ts +28 -0
- package/src/memory/conversation-key-store.ts +15 -0
- package/src/memory/db.ts +81 -0
- package/src/memory/embedding-local.ts +3 -13
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/job-handlers/conflict.ts +22 -2
- package/src/memory/jobs-worker.ts +67 -28
- package/src/memory/runs-store.ts +54 -7
- package/src/memory/schema.ts +20 -0
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +1 -0
- package/src/permissions/checker.ts +48 -44
- package/src/permissions/defaults.ts +11 -0
- package/src/permissions/prompter.ts +0 -4
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +76 -53
- package/src/permissions/types.ts +0 -19
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/providers/retry.ts +12 -37
- package/src/runtime/assistant-event-hub.ts +41 -4
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +71 -0
- package/src/runtime/channel-approvals.ts +145 -0
- package/src/runtime/gateway-client.ts +16 -0
- package/src/runtime/http-server.ts +29 -9
- package/src/runtime/routes/call-routes.ts +52 -2
- package/src/runtime/routes/channel-routes.ts +296 -16
- package/src/runtime/routes/conversation-routes.ts +12 -5
- package/src/runtime/routes/events-routes.ts +97 -28
- package/src/runtime/routes/run-routes.ts +2 -7
- package/src/runtime/run-orchestrator.ts +0 -3
- package/src/schedule/recurrence-engine.ts +26 -2
- package/src/schedule/recurrence-types.ts +1 -1
- package/src/schedule/schedule-store.ts +12 -3
- package/src/security/secret-scanner.ts +7 -0
- package/src/tasks/ephemeral-permissions.ts +0 -2
- package/src/tasks/task-scheduler.ts +2 -1
- package/src/tools/calls/call-start.ts +8 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +6 -135
- package/src/tools/network/web-search.ts +9 -32
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/terminal/parser.ts +16 -18
- package/src/tools/types.ts +4 -11
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +29 -4
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/truncate.ts +1 -1
- package/src/workspace/git-service.ts +129 -112
- package/src/tools/contacts/contact-merge.ts +0 -55
- package/src/tools/contacts/contact-search.ts +0 -58
- package/src/tools/contacts/contact-upsert.ts +0 -64
- package/src/tools/playbooks/index.ts +0 -4
- package/src/tools/playbooks/playbook-create.ts +0 -96
- package/src/tools/playbooks/playbook-delete.ts +0 -52
- package/src/tools/playbooks/playbook-list.ts +0 -74
- package/src/tools/playbooks/playbook-update.ts +0 -111
package/src/config/schema.ts
CHANGED
|
@@ -7,8 +7,19 @@ const VALID_SECRET_ACTIONS = ['redact', 'warn', 'block', 'prompt'] as const;
|
|
|
7
7
|
const VALID_MEMORY_EMBEDDING_PROVIDERS = ['auto', 'local', 'openai', 'gemini', 'ollama'] as const;
|
|
8
8
|
const VALID_SANDBOX_BACKENDS = ['native', 'docker'] as const;
|
|
9
9
|
const VALID_DOCKER_NETWORKS = ['none', 'bridge'] as const;
|
|
10
|
-
const VALID_PERMISSIONS_MODES = ['legacy', 'strict'] as const;
|
|
10
|
+
const VALID_PERMISSIONS_MODES = ['legacy', 'strict', 'workspace'] as const;
|
|
11
11
|
const VALID_CALL_PROVIDERS = ['twilio'] as const;
|
|
12
|
+
const VALID_CALL_VOICE_MODES = ['twilio_standard', 'twilio_elevenlabs_tts', 'elevenlabs_agent'] as const;
|
|
13
|
+
export const VALID_CALLER_IDENTITY_MODES = ['assistant_number', 'user_number'] as const;
|
|
14
|
+
const VALID_CALL_TRANSCRIPTION_PROVIDERS = ['Deepgram', 'Google'] as const;
|
|
15
|
+
const VALID_MEMORY_ITEM_KINDS = [
|
|
16
|
+
'preference', 'profile', 'project', 'decision', 'todo',
|
|
17
|
+
'fact', 'constraint', 'relationship', 'event', 'opinion', 'instruction', 'style',
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
const DEFAULT_CONFLICTABLE_KINDS = [
|
|
21
|
+
'preference', 'profile', 'constraint', 'instruction', 'style',
|
|
22
|
+
] as const;
|
|
12
23
|
|
|
13
24
|
export const TimeoutConfigSchema = z.object({
|
|
14
25
|
shellMaxTimeoutSec: z
|
|
@@ -126,7 +137,7 @@ export const PermissionsConfigSchema = z.object({
|
|
|
126
137
|
.enum(VALID_PERMISSIONS_MODES, {
|
|
127
138
|
error: `permissions.mode must be one of: ${VALID_PERMISSIONS_MODES.join(', ')}`,
|
|
128
139
|
})
|
|
129
|
-
.default('
|
|
140
|
+
.default('workspace'),
|
|
130
141
|
});
|
|
131
142
|
|
|
132
143
|
export const AuditLogConfigSchema = z.object({
|
|
@@ -426,6 +437,11 @@ export const MemoryJobsConfigSchema = z.object({
|
|
|
426
437
|
.int('memory.jobs.workerConcurrency must be an integer')
|
|
427
438
|
.positive('memory.jobs.workerConcurrency must be a positive integer')
|
|
428
439
|
.default(2),
|
|
440
|
+
batchSize: z
|
|
441
|
+
.number({ error: 'memory.jobs.batchSize must be a number' })
|
|
442
|
+
.int('memory.jobs.batchSize must be an integer')
|
|
443
|
+
.positive('memory.jobs.batchSize must be a positive integer')
|
|
444
|
+
.default(10),
|
|
429
445
|
});
|
|
430
446
|
|
|
431
447
|
export const MemoryRetentionConfigSchema = z.object({
|
|
@@ -548,6 +564,17 @@ export const MemoryConflictsConfigSchema = z.object({
|
|
|
548
564
|
.min(0, 'memory.conflicts.relevanceThreshold must be >= 0')
|
|
549
565
|
.max(1, 'memory.conflicts.relevanceThreshold must be <= 1')
|
|
550
566
|
.default(0.3),
|
|
567
|
+
askOnIrrelevantTurns: z
|
|
568
|
+
.boolean({ error: 'memory.conflicts.askOnIrrelevantTurns must be a boolean' })
|
|
569
|
+
.default(false),
|
|
570
|
+
conflictableKinds: z
|
|
571
|
+
.array(
|
|
572
|
+
z.enum(VALID_MEMORY_ITEM_KINDS, {
|
|
573
|
+
error: `memory.conflicts.conflictableKinds entries must be one of: ${VALID_MEMORY_ITEM_KINDS.join(', ')}`,
|
|
574
|
+
}),
|
|
575
|
+
)
|
|
576
|
+
.nonempty({ message: 'memory.conflicts.conflictableKinds must not be empty' })
|
|
577
|
+
.default([...DEFAULT_CONFLICTABLE_KINDS]),
|
|
551
578
|
});
|
|
552
579
|
|
|
553
580
|
export const MemoryProfileConfigSchema = z.object({
|
|
@@ -626,6 +653,7 @@ export const MemoryConfigSchema = z.object({
|
|
|
626
653
|
}),
|
|
627
654
|
jobs: MemoryJobsConfigSchema.default({
|
|
628
655
|
workerConcurrency: 2,
|
|
656
|
+
batchSize: 10,
|
|
629
657
|
}),
|
|
630
658
|
retention: MemoryRetentionConfigSchema.default({
|
|
631
659
|
keepRawForever: true,
|
|
@@ -667,6 +695,8 @@ export const MemoryConfigSchema = z.object({
|
|
|
667
695
|
reaskCooldownTurns: 3,
|
|
668
696
|
resolverLlmTimeoutMs: 12000,
|
|
669
697
|
relevanceThreshold: 0.3,
|
|
698
|
+
askOnIrrelevantTurns: false,
|
|
699
|
+
conflictableKinds: ['preference', 'profile', 'constraint', 'instruction', 'style'],
|
|
670
700
|
}),
|
|
671
701
|
profile: MemoryProfileConfigSchema.default({
|
|
672
702
|
enabled: true,
|
|
@@ -885,6 +915,84 @@ export const CallsSafetyConfigSchema = z.object({
|
|
|
885
915
|
.default([]),
|
|
886
916
|
});
|
|
887
917
|
|
|
918
|
+
export const CallsElevenLabsConfigSchema = z.object({
|
|
919
|
+
voiceId: z
|
|
920
|
+
.string({ error: 'calls.voice.elevenlabs.voiceId must be a string' })
|
|
921
|
+
.default(''),
|
|
922
|
+
voiceModelId: z
|
|
923
|
+
.string({ error: 'calls.voice.elevenlabs.voiceModelId must be a string' })
|
|
924
|
+
.default(''),
|
|
925
|
+
speed: z
|
|
926
|
+
.number({ error: 'calls.voice.elevenlabs.speed must be a number' })
|
|
927
|
+
.min(0.7, 'calls.voice.elevenlabs.speed must be >= 0.7')
|
|
928
|
+
.max(1.2, 'calls.voice.elevenlabs.speed must be <= 1.2')
|
|
929
|
+
.default(1.0),
|
|
930
|
+
stability: z
|
|
931
|
+
.number({ error: 'calls.voice.elevenlabs.stability must be a number' })
|
|
932
|
+
.min(0, 'calls.voice.elevenlabs.stability must be >= 0')
|
|
933
|
+
.max(1, 'calls.voice.elevenlabs.stability must be <= 1')
|
|
934
|
+
.default(0.5),
|
|
935
|
+
similarityBoost: z
|
|
936
|
+
.number({ error: 'calls.voice.elevenlabs.similarityBoost must be a number' })
|
|
937
|
+
.min(0, 'calls.voice.elevenlabs.similarityBoost must be >= 0')
|
|
938
|
+
.max(1, 'calls.voice.elevenlabs.similarityBoost must be <= 1')
|
|
939
|
+
.default(0.75),
|
|
940
|
+
useSpeakerBoost: z
|
|
941
|
+
.boolean({ error: 'calls.voice.elevenlabs.useSpeakerBoost must be a boolean' })
|
|
942
|
+
.default(true),
|
|
943
|
+
agentId: z
|
|
944
|
+
.string({ error: 'calls.voice.elevenlabs.agentId must be a string' })
|
|
945
|
+
.default(''),
|
|
946
|
+
apiBaseUrl: z
|
|
947
|
+
.string({ error: 'calls.voice.elevenlabs.apiBaseUrl must be a string' })
|
|
948
|
+
.default('https://api.elevenlabs.io'),
|
|
949
|
+
registerCallTimeoutMs: z
|
|
950
|
+
.number({ error: 'calls.voice.elevenlabs.registerCallTimeoutMs must be a number' })
|
|
951
|
+
.int('calls.voice.elevenlabs.registerCallTimeoutMs must be an integer')
|
|
952
|
+
.min(1000, 'calls.voice.elevenlabs.registerCallTimeoutMs must be >= 1000')
|
|
953
|
+
.max(15000, 'calls.voice.elevenlabs.registerCallTimeoutMs must be <= 15000')
|
|
954
|
+
.default(5000),
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
export const CallsVoiceConfigSchema = z.object({
|
|
958
|
+
mode: z
|
|
959
|
+
.enum(VALID_CALL_VOICE_MODES, {
|
|
960
|
+
error: `calls.voice.mode must be one of: ${VALID_CALL_VOICE_MODES.join(', ')}`,
|
|
961
|
+
})
|
|
962
|
+
.default('twilio_standard'),
|
|
963
|
+
language: z
|
|
964
|
+
.string({ error: 'calls.voice.language must be a string' })
|
|
965
|
+
.default('en-US'),
|
|
966
|
+
transcriptionProvider: z
|
|
967
|
+
.enum(VALID_CALL_TRANSCRIPTION_PROVIDERS, {
|
|
968
|
+
error: `calls.voice.transcriptionProvider must be one of: ${VALID_CALL_TRANSCRIPTION_PROVIDERS.join(', ')}`,
|
|
969
|
+
})
|
|
970
|
+
.default('Deepgram'),
|
|
971
|
+
fallbackToStandardOnError: z
|
|
972
|
+
.boolean({ error: 'calls.voice.fallbackToStandardOnError must be a boolean' })
|
|
973
|
+
.default(true),
|
|
974
|
+
elevenlabs: CallsElevenLabsConfigSchema.default({
|
|
975
|
+
voiceId: '',
|
|
976
|
+
voiceModelId: '',
|
|
977
|
+
speed: 1.0,
|
|
978
|
+
stability: 0.5,
|
|
979
|
+
similarityBoost: 0.75,
|
|
980
|
+
useSpeakerBoost: true,
|
|
981
|
+
agentId: '',
|
|
982
|
+
apiBaseUrl: 'https://api.elevenlabs.io',
|
|
983
|
+
registerCallTimeoutMs: 5000,
|
|
984
|
+
}),
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
export const CallerIdentityConfigSchema = z.object({
|
|
988
|
+
allowPerCallOverride: z
|
|
989
|
+
.boolean({ error: 'calls.callerIdentity.allowPerCallOverride must be a boolean' })
|
|
990
|
+
.default(true),
|
|
991
|
+
userNumber: z
|
|
992
|
+
.string({ error: 'calls.callerIdentity.userNumber must be a string' })
|
|
993
|
+
.optional(),
|
|
994
|
+
});
|
|
995
|
+
|
|
888
996
|
export const CallsConfigSchema = z.object({
|
|
889
997
|
enabled: z
|
|
890
998
|
.boolean({ error: 'calls.enabled must be a boolean' })
|
|
@@ -913,6 +1021,29 @@ export const CallsConfigSchema = z.object({
|
|
|
913
1021
|
safety: CallsSafetyConfigSchema.default({
|
|
914
1022
|
denyCategories: [],
|
|
915
1023
|
}),
|
|
1024
|
+
voice: CallsVoiceConfigSchema.default({
|
|
1025
|
+
mode: 'twilio_standard',
|
|
1026
|
+
language: 'en-US',
|
|
1027
|
+
transcriptionProvider: 'Deepgram',
|
|
1028
|
+
fallbackToStandardOnError: true,
|
|
1029
|
+
elevenlabs: {
|
|
1030
|
+
voiceId: '',
|
|
1031
|
+
voiceModelId: '',
|
|
1032
|
+
speed: 1.0,
|
|
1033
|
+
stability: 0.5,
|
|
1034
|
+
similarityBoost: 0.75,
|
|
1035
|
+
useSpeakerBoost: true,
|
|
1036
|
+
agentId: '',
|
|
1037
|
+
apiBaseUrl: 'https://api.elevenlabs.io',
|
|
1038
|
+
registerCallTimeoutMs: 5000,
|
|
1039
|
+
},
|
|
1040
|
+
}),
|
|
1041
|
+
model: z
|
|
1042
|
+
.string({ error: 'calls.model must be a string' })
|
|
1043
|
+
.optional(),
|
|
1044
|
+
callerIdentity: CallerIdentityConfigSchema.default({
|
|
1045
|
+
allowPerCallOverride: true,
|
|
1046
|
+
}),
|
|
916
1047
|
});
|
|
917
1048
|
|
|
918
1049
|
export const SkillsConfigSchema = z.object({
|
|
@@ -922,15 +1053,30 @@ export const SkillsConfigSchema = z.object({
|
|
|
922
1053
|
allowBundled: z.array(z.string()).nullable().default(null),
|
|
923
1054
|
});
|
|
924
1055
|
|
|
925
|
-
|
|
1056
|
+
const IngressBaseSchema = z.object({
|
|
926
1057
|
enabled: z
|
|
927
1058
|
.boolean({ error: 'ingress.enabled must be a boolean' })
|
|
928
|
-
.
|
|
1059
|
+
.optional(),
|
|
929
1060
|
publicBaseUrl: z
|
|
930
1061
|
.string({ error: 'ingress.publicBaseUrl must be a string' })
|
|
931
1062
|
.default(''),
|
|
932
1063
|
});
|
|
933
1064
|
|
|
1065
|
+
export const IngressConfigSchema = IngressBaseSchema
|
|
1066
|
+
.default({ publicBaseUrl: '' })
|
|
1067
|
+
.transform((val) => ({
|
|
1068
|
+
...val,
|
|
1069
|
+
// Backward compatibility: if `enabled` was never explicitly set (undefined),
|
|
1070
|
+
// infer it from whether a publicBaseUrl is configured. Existing users who
|
|
1071
|
+
// have a URL but predate the `enabled` field should not have their webhooks
|
|
1072
|
+
// silently disabled on upgrade.
|
|
1073
|
+
//
|
|
1074
|
+
// When publicBaseUrl is empty and enabled is unset, leave enabled as
|
|
1075
|
+
// undefined so getPublicBaseUrl() can still fall through to the
|
|
1076
|
+
// INGRESS_PUBLIC_BASE_URL env-var fallback (env-only setups).
|
|
1077
|
+
enabled: val.enabled ?? (val.publicBaseUrl ? true : undefined),
|
|
1078
|
+
}));
|
|
1079
|
+
|
|
934
1080
|
export const AssistantConfigSchema = z.object({
|
|
935
1081
|
provider: z
|
|
936
1082
|
.enum(VALID_PROVIDERS, {
|
|
@@ -1028,6 +1174,7 @@ export const AssistantConfigSchema = z.object({
|
|
|
1028
1174
|
},
|
|
1029
1175
|
jobs: {
|
|
1030
1176
|
workerConcurrency: 2,
|
|
1177
|
+
batchSize: 10,
|
|
1031
1178
|
},
|
|
1032
1179
|
retention: {
|
|
1033
1180
|
keepRawForever: true,
|
|
@@ -1069,6 +1216,8 @@ export const AssistantConfigSchema = z.object({
|
|
|
1069
1216
|
reaskCooldownTurns: 3,
|
|
1070
1217
|
resolverLlmTimeoutMs: 12000,
|
|
1071
1218
|
relevanceThreshold: 0.3,
|
|
1219
|
+
askOnIrrelevantTurns: false,
|
|
1220
|
+
conflictableKinds: ['preference', 'profile', 'constraint', 'instruction', 'style'],
|
|
1072
1221
|
},
|
|
1073
1222
|
profile: {
|
|
1074
1223
|
enabled: true,
|
|
@@ -1109,7 +1258,7 @@ export const AssistantConfigSchema = z.object({
|
|
|
1109
1258
|
blockIngress: true,
|
|
1110
1259
|
}),
|
|
1111
1260
|
permissions: PermissionsConfigSchema.default({
|
|
1112
|
-
mode: '
|
|
1261
|
+
mode: 'workspace',
|
|
1113
1262
|
}),
|
|
1114
1263
|
auditLog: AuditLogConfigSchema.default({
|
|
1115
1264
|
retentionDays: 0,
|
|
@@ -1178,11 +1327,28 @@ export const AssistantConfigSchema = z.object({
|
|
|
1178
1327
|
safety: {
|
|
1179
1328
|
denyCategories: [],
|
|
1180
1329
|
},
|
|
1330
|
+
voice: {
|
|
1331
|
+
mode: 'twilio_standard',
|
|
1332
|
+
language: 'en-US',
|
|
1333
|
+
transcriptionProvider: 'Deepgram',
|
|
1334
|
+
fallbackToStandardOnError: true,
|
|
1335
|
+
elevenlabs: {
|
|
1336
|
+
voiceId: '',
|
|
1337
|
+
voiceModelId: '',
|
|
1338
|
+
speed: 1.0,
|
|
1339
|
+
stability: 0.5,
|
|
1340
|
+
similarityBoost: 0.75,
|
|
1341
|
+
useSpeakerBoost: true,
|
|
1342
|
+
agentId: '',
|
|
1343
|
+
apiBaseUrl: 'https://api.elevenlabs.io',
|
|
1344
|
+
registerCallTimeoutMs: 5000,
|
|
1345
|
+
},
|
|
1346
|
+
},
|
|
1347
|
+
callerIdentity: {
|
|
1348
|
+
allowPerCallOverride: true,
|
|
1349
|
+
},
|
|
1181
1350
|
}),
|
|
1182
|
-
ingress: IngressConfigSchema
|
|
1183
|
-
enabled: false,
|
|
1184
|
-
publicBaseUrl: '',
|
|
1185
|
-
}),
|
|
1351
|
+
ingress: IngressConfigSchema,
|
|
1186
1352
|
}).superRefine((config, ctx) => {
|
|
1187
1353
|
if (config.contextWindow.targetInputTokens >= config.contextWindow.maxInputTokens) {
|
|
1188
1354
|
ctx.addIssue({
|
|
@@ -1243,4 +1409,7 @@ export type WorkspaceGitConfig = z.infer<typeof WorkspaceGitConfigSchema>;
|
|
|
1243
1409
|
export type CallsConfig = z.infer<typeof CallsConfigSchema>;
|
|
1244
1410
|
export type CallsDisclosureConfig = z.infer<typeof CallsDisclosureConfigSchema>;
|
|
1245
1411
|
export type CallsSafetyConfig = z.infer<typeof CallsSafetyConfigSchema>;
|
|
1412
|
+
export type CallsVoiceConfig = z.infer<typeof CallsVoiceConfigSchema>;
|
|
1413
|
+
export type CallsElevenLabsConfig = z.infer<typeof CallsElevenLabsConfigSchema>;
|
|
1414
|
+
export type CallerIdentityConfig = z.infer<typeof CallerIdentityConfigSchema>;
|
|
1246
1415
|
export type IngressConfig = z.infer<typeof IngressConfigSchema>;
|
package/src/config/types.ts
CHANGED
|
@@ -13,90 +13,85 @@ You are helping your user connect a Telegram bot to the Vellum Assistant gateway
|
|
|
13
13
|
1. **Bot token** from Telegram's @BotFather (the user provides this)
|
|
14
14
|
2. **Gateway webhook URL** — derived from the canonical ingress setting: `${ingress.publicBaseUrl}/webhooks/telegram`. The gateway is the only publicly reachable endpoint; Telegram sends webhooks to the gateway, which validates and forwards them to the assistant runtime internally. If `ingress.publicBaseUrl` is not configured, load and execute the **public-ingress** skill first (`skill_load` with `skill: "public-ingress"`) to set up an ngrok tunnel and persist the URL before continuing.
|
|
15
15
|
|
|
16
|
-
If the user has already
|
|
16
|
+
**IMPORTANT — Secure credential collection only:** Never use a bot token that was pasted in plaintext chat. Always collect the bot token through the secure credential prompt flow using `credential_store` with `action: "prompt"` and `service: "telegram"`, `field: "bot_token"`. If the user has already pasted a token in the conversation, inform them that for security reasons you cannot use tokens shared in chat and must collect it through the secure prompt instead.
|
|
17
17
|
|
|
18
18
|
## Setup Steps
|
|
19
19
|
|
|
20
|
-
### Step 1:
|
|
20
|
+
### Step 1: Collect the Bot Token Securely
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Collect the bot token through the secure credential prompt:
|
|
23
|
+
- Call `credential_store` with `action: "prompt"`, `service: "telegram"`, `field: "bot_token"`, `label: "Telegram Bot Token"`, `description: "Enter the bot token you received from @BotFather"`, and `placeholder: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"`.
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
export default async (input: { token: string }) => {
|
|
26
|
-
const res = await fetch(`https://api.telegram.org/bot${input.token}/getMe`, { method: 'POST' });
|
|
27
|
-
return res.json();
|
|
28
|
-
};
|
|
29
|
-
```
|
|
25
|
+
The token is collected securely via a system-level prompt and is never exposed in plaintext chat.
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
### Step 2: Configure via Daemon
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
After the token is collected, send it to the daemon's `telegram_config` handler which validates, stores, and configures everything in one step:
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
- Send the `telegram_config` IPC message with `action: "set"`. The daemon retrieves the token from secure storage internally when `botToken` is not provided in the message — you do not need to retrieve it yourself.
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
The daemon's `telegram_config set` handler automatically:
|
|
34
|
+
- Validates the token by calling the Telegram `getMe` API
|
|
35
|
+
- Stores the bot token in secure storage with bot username metadata
|
|
36
|
+
- Generates a webhook secret if one does not already exist
|
|
37
|
+
- Triggers an immediate gateway webhook reconcile
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
import { randomUUID } from 'node:crypto';
|
|
41
|
-
export default () => ({ secret: randomUUID() });
|
|
42
|
-
```
|
|
39
|
+
If the token is invalid, the daemon returns an error. Tell the user and ask them to re-enter the token via the secure prompt.
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
### Step 3: Webhook Registration (Automatic)
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
Manual webhook registration is no longer required. The gateway automatically reconciles the Telegram webhook on startup and whenever credentials change. It compares the current webhook URL against `${INGRESS_PUBLIC_BASE_URL}/webhooks/telegram` and updates it if needed, including the webhook secret and allowed updates.
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
export default async (input: { token: string; url: string; secret: string }) => {
|
|
52
|
-
const res = await fetch(`https://api.telegram.org/bot${input.token}/setWebhook`, {
|
|
53
|
-
method: 'POST',
|
|
54
|
-
headers: { 'Content-Type': 'application/json' },
|
|
55
|
-
body: JSON.stringify({
|
|
56
|
-
url: input.url,
|
|
57
|
-
secret_token: input.secret,
|
|
58
|
-
allowed_updates: ['message', 'edited_message'],
|
|
59
|
-
}),
|
|
60
|
-
});
|
|
61
|
-
return res.json();
|
|
62
|
-
};
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Verify the response has `ok: true`.
|
|
45
|
+
If the webhook secret changes (e.g., secret rotation), the gateway's credential watcher detects the change and re-registers the webhook automatically. If the ingress URL changes (e.g., tunnel restart), the assistant daemon triggers an immediate internal reconcile so the webhook re-registers automatically without a gateway restart.
|
|
66
46
|
|
|
67
47
|
### Step 4: Register Bot Commands
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
export default async (input: { token: string }) => {
|
|
73
|
-
const res = await fetch(`https://api.telegram.org/bot${input.token}/setMyCommands`, {
|
|
74
|
-
method: 'POST',
|
|
75
|
-
headers: { 'Content-Type': 'application/json' },
|
|
76
|
-
body: JSON.stringify({
|
|
77
|
-
commands: [{ command: 'new', description: 'Start a new conversation' }],
|
|
78
|
-
}),
|
|
79
|
-
});
|
|
80
|
-
return res.json();
|
|
81
|
-
};
|
|
82
|
-
```
|
|
49
|
+
Send the `telegram_config` IPC message with `action: "set_commands"` to register the `/new` command. The daemon handles token retrieval from secure storage internally — you do not need to retrieve it yourself.
|
|
83
50
|
|
|
84
|
-
### Step 5:
|
|
51
|
+
### Step 5: Validate Routing Configuration
|
|
85
52
|
|
|
86
|
-
|
|
53
|
+
Verify that the gateway routing is configured to deliver inbound messages to the assistant:
|
|
87
54
|
|
|
88
|
-
|
|
89
|
-
|
|
55
|
+
- In **single-assistant mode** (the default local deployment), routing is automatically configured. The CLI sets `GATEWAY_UNMAPPED_POLICY=default` and `GATEWAY_DEFAULT_ASSISTANT_ID` to the current assistant's ID when starting the gateway, so no manual routing configuration is needed.
|
|
56
|
+
- In **multi-assistant mode**, the operator must set `GATEWAY_ASSISTANT_ROUTING_JSON` to map specific chat IDs or user IDs to assistant IDs, or configure a default assistant via `GATEWAY_DEFAULT_ASSISTANT_ID` with `GATEWAY_UNMAPPED_POLICY=default`.
|
|
90
57
|
|
|
91
|
-
|
|
92
|
-
- action: `store`, service: `telegram`, field: `webhook_secret`, value: the generated secret
|
|
58
|
+
If routing is misconfigured, inbound Telegram messages will be rejected and the gateway will send a visible notice to the chat explaining the issue (rate-limited to once per 5 minutes per chat).
|
|
93
59
|
|
|
94
60
|
### Step 6: Report Success
|
|
95
61
|
|
|
96
62
|
Summarize what was done:
|
|
97
|
-
- Bot verified
|
|
98
|
-
- Webhook
|
|
63
|
+
- Bot verified and credentials stored securely via daemon
|
|
64
|
+
- Webhook registration: handled automatically by the gateway
|
|
99
65
|
- Bot commands registered: /new
|
|
100
|
-
-
|
|
66
|
+
- Routing configuration validated
|
|
67
|
+
|
|
68
|
+
The gateway automatically detects credentials from the vault, reconciles the Telegram webhook registration, and begins accepting Telegram webhooks shortly. In single-assistant mode, routing is automatically configured — no manual environment variable configuration or webhook registration is needed. If the webhook secret changes later, the gateway's credential watcher will automatically re-register the webhook. If the ingress URL changes (e.g., tunnel restart), the assistant daemon triggers an immediate internal reconcile so the webhook re-registers automatically without a gateway restart.
|
|
69
|
+
|
|
70
|
+
## Bot-Account Limitations
|
|
71
|
+
|
|
72
|
+
Telegram bot accounts have inherent limitations imposed by the Bot API:
|
|
73
|
+
|
|
74
|
+
- **No arbitrary messaging**: Bots cannot initiate conversations with users who have not first interacted with the bot (sent `/start` or added it to a group). Messaging arbitrary phone numbers is not possible.
|
|
75
|
+
- **No conversation listing**: The Bot API does not expose a method to enumerate the chats a bot belongs to.
|
|
76
|
+
- **No message history retrieval**: Bots cannot fetch past messages from a chat.
|
|
77
|
+
- **No message search**: No search API is available for bots.
|
|
78
|
+
|
|
79
|
+
These limitations apply to all Telegram bots regardless of configuration. Future support for MTProto user-account sessions may lift some of these restrictions.
|
|
80
|
+
|
|
81
|
+
## Automated vs Manual Steps
|
|
82
|
+
|
|
83
|
+
The following steps are now **automated** by the gateway and CLI:
|
|
84
|
+
|
|
85
|
+
| Step | Status | Details |
|
|
86
|
+
|------|--------|---------|
|
|
87
|
+
| Webhook registration | Automated | The gateway reconciles the webhook URL on startup and when credentials change |
|
|
88
|
+
| Routing configuration | Automated (single-assistant) | The CLI sets `GATEWAY_UNMAPPED_POLICY=default` and `GATEWAY_DEFAULT_ASSISTANT_ID` automatically |
|
|
89
|
+
| Credential detection | Automated | The gateway watches the credential vault for changes |
|
|
90
|
+
|
|
91
|
+
The following steps still require **manual** action:
|
|
101
92
|
|
|
102
|
-
|
|
93
|
+
| Step | Details |
|
|
94
|
+
|------|---------|
|
|
95
|
+
| Bot token from @BotFather | User must create a bot and provide the token via secure prompt |
|
|
96
|
+
| Bot command registration | Registered via the setup skill (Step 4 above) |
|
|
97
|
+
| Multi-assistant routing | Requires manual `GATEWAY_ASSISTANT_ROUTING_JSON` configuration |
|
|
@@ -320,8 +320,10 @@ export function drainDirectiveDisplayBuffer(buffer: string): DirectiveDisplayDra
|
|
|
320
320
|
// streaming mode more data may arrive in the next chunk — eagerly
|
|
321
321
|
// trimming would merge words across the directive boundary.
|
|
322
322
|
const nextChar = buffer[end + 2];
|
|
323
|
-
if (emitText.endsWith('\n') &&
|
|
324
|
-
emitText = emitText.slice(0, -
|
|
323
|
+
if (emitText.endsWith('\r\n') && nextChar === '\r') {
|
|
324
|
+
emitText = emitText.slice(0, -2); // trim full \r\n
|
|
325
|
+
} else if (emitText.endsWith('\n') && (nextChar === '\n' || nextChar === '\r')) {
|
|
326
|
+
emitText = emitText.slice(0, -1); // trim \n
|
|
325
327
|
}
|
|
326
328
|
}
|
|
327
329
|
|
|
@@ -16,8 +16,13 @@ import type {
|
|
|
16
16
|
ShareAppCloudRequest,
|
|
17
17
|
GalleryInstallRequest,
|
|
18
18
|
AppUpdatePreviewRequest,
|
|
19
|
+
AppHistoryRequest,
|
|
20
|
+
AppDiffRequest,
|
|
21
|
+
AppFileAtVersionRequest,
|
|
22
|
+
AppRestoreRequest,
|
|
19
23
|
UiSurfaceShow,
|
|
20
24
|
} from '../ipc-protocol.js';
|
|
25
|
+
import { getAppHistory, getAppDiff, getAppFileAtVersion, restoreAppVersion } from '../../memory/app-git-service.js';
|
|
21
26
|
import { log, compareSemver, createSigningCallback, defineHandlers, type HandlerContext } from './shared.js';
|
|
22
27
|
|
|
23
28
|
export function handleAppDataRequest(
|
|
@@ -445,6 +450,66 @@ export function handleGalleryInstall(
|
|
|
445
450
|
}
|
|
446
451
|
}
|
|
447
452
|
|
|
453
|
+
export async function handleAppHistory(
|
|
454
|
+
msg: AppHistoryRequest,
|
|
455
|
+
socket: net.Socket,
|
|
456
|
+
ctx: HandlerContext,
|
|
457
|
+
): Promise<void> {
|
|
458
|
+
try {
|
|
459
|
+
const versions = await getAppHistory(msg.appId, msg.limit);
|
|
460
|
+
ctx.send(socket, { type: 'app_history_response', appId: msg.appId, versions });
|
|
461
|
+
} catch (err) {
|
|
462
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
463
|
+
log.error({ err, appId: msg.appId }, 'Failed to get app history');
|
|
464
|
+
ctx.send(socket, { type: 'error', message: `Failed to get app history: ${message}` });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export async function handleAppDiff(
|
|
469
|
+
msg: AppDiffRequest,
|
|
470
|
+
socket: net.Socket,
|
|
471
|
+
ctx: HandlerContext,
|
|
472
|
+
): Promise<void> {
|
|
473
|
+
try {
|
|
474
|
+
const diff = await getAppDiff(msg.appId, msg.fromCommit, msg.toCommit);
|
|
475
|
+
ctx.send(socket, { type: 'app_diff_response', appId: msg.appId, diff });
|
|
476
|
+
} catch (err) {
|
|
477
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
478
|
+
log.error({ err, appId: msg.appId }, 'Failed to get app diff');
|
|
479
|
+
ctx.send(socket, { type: 'error', message: `Failed to get app diff: ${message}` });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export async function handleAppFileAtVersion(
|
|
484
|
+
msg: AppFileAtVersionRequest,
|
|
485
|
+
socket: net.Socket,
|
|
486
|
+
ctx: HandlerContext,
|
|
487
|
+
): Promise<void> {
|
|
488
|
+
try {
|
|
489
|
+
const content = await getAppFileAtVersion(msg.appId, msg.path, msg.commitHash);
|
|
490
|
+
ctx.send(socket, { type: 'app_file_at_version_response', appId: msg.appId, path: msg.path, content });
|
|
491
|
+
} catch (err) {
|
|
492
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
493
|
+
log.error({ err, appId: msg.appId }, 'Failed to get app file at version');
|
|
494
|
+
ctx.send(socket, { type: 'error', message: `Failed to get app file at version: ${message}` });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export async function handleAppRestore(
|
|
499
|
+
msg: AppRestoreRequest,
|
|
500
|
+
socket: net.Socket,
|
|
501
|
+
ctx: HandlerContext,
|
|
502
|
+
): Promise<void> {
|
|
503
|
+
try {
|
|
504
|
+
await restoreAppVersion(msg.appId, msg.commitHash);
|
|
505
|
+
ctx.send(socket, { type: 'app_restore_response', success: true });
|
|
506
|
+
} catch (err) {
|
|
507
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
508
|
+
log.error({ err, appId: msg.appId }, 'Failed to restore app version');
|
|
509
|
+
ctx.send(socket, { type: 'app_restore_response', success: false, error: message });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
448
513
|
export const appHandlers = defineHandlers({
|
|
449
514
|
app_data_request: handleAppDataRequest,
|
|
450
515
|
app_open_request: handleAppOpenRequest,
|
|
@@ -458,4 +523,8 @@ export const appHandlers = defineHandlers({
|
|
|
458
523
|
bundle_app: handleBundleApp,
|
|
459
524
|
gallery_list: (_msg, socket, ctx) => handleGalleryList(socket, ctx),
|
|
460
525
|
gallery_install: handleGalleryInstall,
|
|
526
|
+
app_history_request: handleAppHistory,
|
|
527
|
+
app_diff_request: handleAppDiff,
|
|
528
|
+
app_file_at_version_request: handleAppFileAtVersion,
|
|
529
|
+
app_restore_request: handleAppRestore,
|
|
461
530
|
});
|