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.
- 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 +113 -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 +137 -18
- 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 +62 -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 +27 -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 +4 -4
- 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 +93 -7
- package/src/calls/call-store.ts +6 -0
- package/src/calls/elevenlabs-client.ts +8 -0
- package/src/calls/elevenlabs-config.ts +7 -5
- package/src/calls/twilio-provider.ts +91 -0
- package/src/calls/twilio-routes.ts +32 -37
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-quality.ts +29 -7
- 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 +142 -34
- 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 +10 -4
- package/src/config/schema.ts +80 -21
- package/src/config/types.ts +1 -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/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/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,10 +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
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;
|
|
13
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;
|
|
14
23
|
|
|
15
24
|
export const TimeoutConfigSchema = z.object({
|
|
16
25
|
shellMaxTimeoutSec: z
|
|
@@ -128,7 +137,7 @@ export const PermissionsConfigSchema = z.object({
|
|
|
128
137
|
.enum(VALID_PERMISSIONS_MODES, {
|
|
129
138
|
error: `permissions.mode must be one of: ${VALID_PERMISSIONS_MODES.join(', ')}`,
|
|
130
139
|
})
|
|
131
|
-
.default('
|
|
140
|
+
.default('workspace'),
|
|
132
141
|
});
|
|
133
142
|
|
|
134
143
|
export const AuditLogConfigSchema = z.object({
|
|
@@ -428,6 +437,11 @@ export const MemoryJobsConfigSchema = z.object({
|
|
|
428
437
|
.int('memory.jobs.workerConcurrency must be an integer')
|
|
429
438
|
.positive('memory.jobs.workerConcurrency must be a positive integer')
|
|
430
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),
|
|
431
445
|
});
|
|
432
446
|
|
|
433
447
|
export const MemoryRetentionConfigSchema = z.object({
|
|
@@ -550,6 +564,17 @@ export const MemoryConflictsConfigSchema = z.object({
|
|
|
550
564
|
.min(0, 'memory.conflicts.relevanceThreshold must be >= 0')
|
|
551
565
|
.max(1, 'memory.conflicts.relevanceThreshold must be <= 1')
|
|
552
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]),
|
|
553
578
|
});
|
|
554
579
|
|
|
555
580
|
export const MemoryProfileConfigSchema = z.object({
|
|
@@ -628,6 +653,7 @@ export const MemoryConfigSchema = z.object({
|
|
|
628
653
|
}),
|
|
629
654
|
jobs: MemoryJobsConfigSchema.default({
|
|
630
655
|
workerConcurrency: 2,
|
|
656
|
+
batchSize: 10,
|
|
631
657
|
}),
|
|
632
658
|
retention: MemoryRetentionConfigSchema.default({
|
|
633
659
|
keepRawForever: true,
|
|
@@ -669,6 +695,8 @@ export const MemoryConfigSchema = z.object({
|
|
|
669
695
|
reaskCooldownTurns: 3,
|
|
670
696
|
resolverLlmTimeoutMs: 12000,
|
|
671
697
|
relevanceThreshold: 0.3,
|
|
698
|
+
askOnIrrelevantTurns: false,
|
|
699
|
+
conflictableKinds: ['preference', 'profile', 'constraint', 'instruction', 'style'],
|
|
672
700
|
}),
|
|
673
701
|
profile: MemoryProfileConfigSchema.default({
|
|
674
702
|
enabled: true,
|
|
@@ -893,7 +921,12 @@ export const CallsElevenLabsConfigSchema = z.object({
|
|
|
893
921
|
.default(''),
|
|
894
922
|
voiceModelId: z
|
|
895
923
|
.string({ error: 'calls.voice.elevenlabs.voiceModelId must be a string' })
|
|
896
|
-
.default('
|
|
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),
|
|
897
930
|
stability: z
|
|
898
931
|
.number({ error: 'calls.voice.elevenlabs.stability must be a number' })
|
|
899
932
|
.min(0, 'calls.voice.elevenlabs.stability must be >= 0')
|
|
@@ -904,11 +937,6 @@ export const CallsElevenLabsConfigSchema = z.object({
|
|
|
904
937
|
.min(0, 'calls.voice.elevenlabs.similarityBoost must be >= 0')
|
|
905
938
|
.max(1, 'calls.voice.elevenlabs.similarityBoost must be <= 1')
|
|
906
939
|
.default(0.75),
|
|
907
|
-
style: z
|
|
908
|
-
.number({ error: 'calls.voice.elevenlabs.style must be a number' })
|
|
909
|
-
.min(0, 'calls.voice.elevenlabs.style must be >= 0')
|
|
910
|
-
.max(1, 'calls.voice.elevenlabs.style must be <= 1')
|
|
911
|
-
.default(0.0),
|
|
912
940
|
useSpeakerBoost: z
|
|
913
941
|
.boolean({ error: 'calls.voice.elevenlabs.useSpeakerBoost must be a boolean' })
|
|
914
942
|
.default(true),
|
|
@@ -945,10 +973,10 @@ export const CallsVoiceConfigSchema = z.object({
|
|
|
945
973
|
.default(true),
|
|
946
974
|
elevenlabs: CallsElevenLabsConfigSchema.default({
|
|
947
975
|
voiceId: '',
|
|
948
|
-
voiceModelId: '
|
|
976
|
+
voiceModelId: '',
|
|
977
|
+
speed: 1.0,
|
|
949
978
|
stability: 0.5,
|
|
950
979
|
similarityBoost: 0.75,
|
|
951
|
-
style: 0.0,
|
|
952
980
|
useSpeakerBoost: true,
|
|
953
981
|
agentId: '',
|
|
954
982
|
apiBaseUrl: 'https://api.elevenlabs.io',
|
|
@@ -956,6 +984,15 @@ export const CallsVoiceConfigSchema = z.object({
|
|
|
956
984
|
}),
|
|
957
985
|
});
|
|
958
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
|
+
|
|
959
996
|
export const CallsConfigSchema = z.object({
|
|
960
997
|
enabled: z
|
|
961
998
|
.boolean({ error: 'calls.enabled must be a boolean' })
|
|
@@ -991,10 +1028,10 @@ export const CallsConfigSchema = z.object({
|
|
|
991
1028
|
fallbackToStandardOnError: true,
|
|
992
1029
|
elevenlabs: {
|
|
993
1030
|
voiceId: '',
|
|
994
|
-
voiceModelId: '
|
|
1031
|
+
voiceModelId: '',
|
|
1032
|
+
speed: 1.0,
|
|
995
1033
|
stability: 0.5,
|
|
996
1034
|
similarityBoost: 0.75,
|
|
997
|
-
style: 0.0,
|
|
998
1035
|
useSpeakerBoost: true,
|
|
999
1036
|
agentId: '',
|
|
1000
1037
|
apiBaseUrl: 'https://api.elevenlabs.io',
|
|
@@ -1004,6 +1041,9 @@ export const CallsConfigSchema = z.object({
|
|
|
1004
1041
|
model: z
|
|
1005
1042
|
.string({ error: 'calls.model must be a string' })
|
|
1006
1043
|
.optional(),
|
|
1044
|
+
callerIdentity: CallerIdentityConfigSchema.default({
|
|
1045
|
+
allowPerCallOverride: true,
|
|
1046
|
+
}),
|
|
1007
1047
|
});
|
|
1008
1048
|
|
|
1009
1049
|
export const SkillsConfigSchema = z.object({
|
|
@@ -1013,15 +1053,30 @@ export const SkillsConfigSchema = z.object({
|
|
|
1013
1053
|
allowBundled: z.array(z.string()).nullable().default(null),
|
|
1014
1054
|
});
|
|
1015
1055
|
|
|
1016
|
-
|
|
1056
|
+
const IngressBaseSchema = z.object({
|
|
1017
1057
|
enabled: z
|
|
1018
1058
|
.boolean({ error: 'ingress.enabled must be a boolean' })
|
|
1019
|
-
.
|
|
1059
|
+
.optional(),
|
|
1020
1060
|
publicBaseUrl: z
|
|
1021
1061
|
.string({ error: 'ingress.publicBaseUrl must be a string' })
|
|
1022
1062
|
.default(''),
|
|
1023
1063
|
});
|
|
1024
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
|
+
|
|
1025
1080
|
export const AssistantConfigSchema = z.object({
|
|
1026
1081
|
provider: z
|
|
1027
1082
|
.enum(VALID_PROVIDERS, {
|
|
@@ -1119,6 +1174,7 @@ export const AssistantConfigSchema = z.object({
|
|
|
1119
1174
|
},
|
|
1120
1175
|
jobs: {
|
|
1121
1176
|
workerConcurrency: 2,
|
|
1177
|
+
batchSize: 10,
|
|
1122
1178
|
},
|
|
1123
1179
|
retention: {
|
|
1124
1180
|
keepRawForever: true,
|
|
@@ -1160,6 +1216,8 @@ export const AssistantConfigSchema = z.object({
|
|
|
1160
1216
|
reaskCooldownTurns: 3,
|
|
1161
1217
|
resolverLlmTimeoutMs: 12000,
|
|
1162
1218
|
relevanceThreshold: 0.3,
|
|
1219
|
+
askOnIrrelevantTurns: false,
|
|
1220
|
+
conflictableKinds: ['preference', 'profile', 'constraint', 'instruction', 'style'],
|
|
1163
1221
|
},
|
|
1164
1222
|
profile: {
|
|
1165
1223
|
enabled: true,
|
|
@@ -1200,7 +1258,7 @@ export const AssistantConfigSchema = z.object({
|
|
|
1200
1258
|
blockIngress: true,
|
|
1201
1259
|
}),
|
|
1202
1260
|
permissions: PermissionsConfigSchema.default({
|
|
1203
|
-
mode: '
|
|
1261
|
+
mode: 'workspace',
|
|
1204
1262
|
}),
|
|
1205
1263
|
auditLog: AuditLogConfigSchema.default({
|
|
1206
1264
|
retentionDays: 0,
|
|
@@ -1276,21 +1334,21 @@ export const AssistantConfigSchema = z.object({
|
|
|
1276
1334
|
fallbackToStandardOnError: true,
|
|
1277
1335
|
elevenlabs: {
|
|
1278
1336
|
voiceId: '',
|
|
1279
|
-
voiceModelId: '
|
|
1337
|
+
voiceModelId: '',
|
|
1338
|
+
speed: 1.0,
|
|
1280
1339
|
stability: 0.5,
|
|
1281
1340
|
similarityBoost: 0.75,
|
|
1282
|
-
style: 0.0,
|
|
1283
1341
|
useSpeakerBoost: true,
|
|
1284
1342
|
agentId: '',
|
|
1285
1343
|
apiBaseUrl: 'https://api.elevenlabs.io',
|
|
1286
1344
|
registerCallTimeoutMs: 5000,
|
|
1287
1345
|
},
|
|
1288
1346
|
},
|
|
1347
|
+
callerIdentity: {
|
|
1348
|
+
allowPerCallOverride: true,
|
|
1349
|
+
},
|
|
1289
1350
|
}),
|
|
1290
|
-
ingress: IngressConfigSchema
|
|
1291
|
-
enabled: false,
|
|
1292
|
-
publicBaseUrl: '',
|
|
1293
|
-
}),
|
|
1351
|
+
ingress: IngressConfigSchema,
|
|
1294
1352
|
}).superRefine((config, ctx) => {
|
|
1295
1353
|
if (config.contextWindow.targetInputTokens >= config.contextWindow.maxInputTokens) {
|
|
1296
1354
|
ctx.addIssue({
|
|
@@ -1353,4 +1411,5 @@ export type CallsDisclosureConfig = z.infer<typeof CallsDisclosureConfigSchema>;
|
|
|
1353
1411
|
export type CallsSafetyConfig = z.infer<typeof CallsSafetyConfigSchema>;
|
|
1354
1412
|
export type CallsVoiceConfig = z.infer<typeof CallsVoiceConfigSchema>;
|
|
1355
1413
|
export type CallsElevenLabsConfig = z.infer<typeof CallsElevenLabsConfigSchema>;
|
|
1414
|
+
export type CallerIdentityConfig = z.infer<typeof CallerIdentityConfigSchema>;
|
|
1356
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
|
});
|