vellum 0.2.1 → 0.2.7
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 +15 -2
- package/bun.lock +71 -100
- package/package.json +5 -3
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +4 -3
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +130 -4
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +305 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twilio-config.test.ts +221 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +71 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-regressions.test.ts +100 -2
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +303 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +5 -3
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-conflict-gate.test.ts +28 -25
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +71 -48
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +218 -3
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/__tests__/twilio-webhook-urls.test.ts +162 -0
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +8 -8
- package/src/calls/twilio-provider.ts +13 -9
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/twilio-webhook-urls.ts +50 -0
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +270 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +82 -23
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +34 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +165 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -5
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +205 -5
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -277
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +384 -68
- package/src/daemon/ipc-contract-inventory.json +32 -4
- package/src/daemon/ipc-contract.ts +156 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +75 -10
- package/src/daemon/server.ts +143 -26
- package/src/daemon/session-agent-loop.ts +922 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-conflict-gate.ts +18 -109
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1153
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/conflict-intent.ts +114 -0
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +96 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/job-handlers/conflict.ts +23 -1
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +23 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/gateway-client.ts +36 -0
- package/src/runtime/http-server.ts +166 -22
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +125 -88
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +293 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- package/src/tools/claude-code/claude-code.ts +207 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +20 -4
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +269 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -10,8 +10,9 @@ import { resolve } from 'node:path';
|
|
|
10
10
|
import { timingSafeEqual } from 'node:crypto';
|
|
11
11
|
import { ConfigError, IngressBlockedError } from '../util/errors.js';
|
|
12
12
|
import { getLogger } from '../util/logger.js';
|
|
13
|
-
import { getSecureKey } from '../security/secure-keys.js';
|
|
14
13
|
import { TwilioConversationRelayProvider } from '../calls/twilio-provider.js';
|
|
14
|
+
import { loadConfig } from '../config/loader.js';
|
|
15
|
+
import { getWebhookBaseUrl } from '../calls/twilio-webhook-urls.js';
|
|
15
16
|
import type { RunOrchestrator } from './run-orchestrator.js';
|
|
16
17
|
|
|
17
18
|
// Route handlers — grouped by domain
|
|
@@ -39,6 +40,10 @@ import {
|
|
|
39
40
|
handleReplayDeadLetters,
|
|
40
41
|
} from './routes/channel-routes.js';
|
|
41
42
|
import * as channelDeliveryStore from '../memory/channel-delivery-store.js';
|
|
43
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
44
|
+
import * as attachmentsStore from '../memory/attachments-store.js';
|
|
45
|
+
import { renderHistoryContent } from '../daemon/handlers.js';
|
|
46
|
+
import { deliverChannelReply } from './gateway-client.js';
|
|
42
47
|
import {
|
|
43
48
|
handleServePage,
|
|
44
49
|
handleShareApp,
|
|
@@ -47,11 +52,16 @@ import {
|
|
|
47
52
|
handleDeleteSharedApp,
|
|
48
53
|
} from './routes/app-routes.js';
|
|
49
54
|
import { handleAddSecret } from './routes/secret-routes.js';
|
|
55
|
+
import {
|
|
56
|
+
handleStartCall,
|
|
57
|
+
handleGetCallStatus,
|
|
58
|
+
handleCancelCall,
|
|
59
|
+
handleAnswerCall,
|
|
60
|
+
} from './routes/call-routes.js';
|
|
50
61
|
import {
|
|
51
62
|
handleVoiceWebhook,
|
|
52
63
|
handleStatusCallback,
|
|
53
64
|
handleConnectAction,
|
|
54
|
-
handleCallAnswer,
|
|
55
65
|
} from '../calls/twilio-routes.js';
|
|
56
66
|
import { RelayConnection, activeRelayConnections } from '../calls/relay-server.js';
|
|
57
67
|
import type { RelayWebSocketData } from '../calls/relay-server.js';
|
|
@@ -113,25 +123,50 @@ function getDiskSpaceInfo(): DiskSpaceInfo | null {
|
|
|
113
123
|
*/
|
|
114
124
|
const TWILIO_WEBHOOK_RE = /^\/v1\/(?:assistants\/[^/]+\/)?calls\/twilio\/(.+)$/;
|
|
115
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Gateway-compatible Twilio webhook paths:
|
|
128
|
+
* /webhooks/twilio/<subpath>
|
|
129
|
+
*
|
|
130
|
+
* Maps gateway path segments to the internal subpath names used by the
|
|
131
|
+
* dispatcher below (e.g. "voice" -> "voice-webhook").
|
|
132
|
+
*/
|
|
133
|
+
const TWILIO_GATEWAY_WEBHOOK_RE = /^\/webhooks\/twilio\/(.+)$/;
|
|
134
|
+
const GATEWAY_SUBPATH_MAP: Record<string, string> = {
|
|
135
|
+
voice: 'voice-webhook',
|
|
136
|
+
status: 'status',
|
|
137
|
+
'connect-action': 'connect-action',
|
|
138
|
+
};
|
|
139
|
+
|
|
116
140
|
/**
|
|
117
141
|
* Validate a Twilio webhook request's X-Twilio-Signature header.
|
|
118
142
|
*
|
|
119
143
|
* Returns the raw body text on success so callers can reconstruct the Request
|
|
120
144
|
* for downstream handlers (which also need to read the body).
|
|
121
145
|
* Returns a 403 Response if signature validation fails.
|
|
122
|
-
*
|
|
146
|
+
*
|
|
147
|
+
* Fail-closed: if the auth token is not configured, the request is rejected
|
|
148
|
+
* with 403 rather than silently skipping validation. An explicit local-dev
|
|
149
|
+
* bypass is available via TWILIO_WEBHOOK_VALIDATION_DISABLED=true.
|
|
123
150
|
*/
|
|
124
151
|
async function validateTwilioWebhook(
|
|
125
152
|
req: Request,
|
|
126
153
|
): Promise<{ body: string } | Response> {
|
|
127
154
|
const rawBody = await req.text();
|
|
128
|
-
const authToken = getSecureKey('twilio_auth_token');
|
|
129
155
|
|
|
130
|
-
|
|
131
|
-
|
|
156
|
+
// Allow explicit local-dev bypass — must be exactly "true"
|
|
157
|
+
if (process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED === 'true') {
|
|
158
|
+
log.warn('Twilio webhook signature validation explicitly disabled via TWILIO_WEBHOOK_VALIDATION_DISABLED');
|
|
132
159
|
return { body: rawBody };
|
|
133
160
|
}
|
|
134
161
|
|
|
162
|
+
const authToken = TwilioConversationRelayProvider.getAuthToken();
|
|
163
|
+
|
|
164
|
+
// Fail-closed: reject if no auth token is configured
|
|
165
|
+
if (!authToken) {
|
|
166
|
+
log.error('Twilio auth token not configured — rejecting webhook request (fail-closed)');
|
|
167
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
168
|
+
}
|
|
169
|
+
|
|
135
170
|
const signature = req.headers.get('x-twilio-signature');
|
|
136
171
|
if (!signature) {
|
|
137
172
|
log.warn('Twilio webhook request missing X-Twilio-Signature header');
|
|
@@ -149,16 +184,22 @@ async function validateTwilioWebhook(
|
|
|
149
184
|
// Behind proxies/gateways, req.url is the local server URL (e.g.
|
|
150
185
|
// http://127.0.0.1:7821/...) which differs from the public URL Twilio
|
|
151
186
|
// used to compute the HMAC-SHA1 signature.
|
|
152
|
-
|
|
187
|
+
let publicBaseUrl: string | undefined;
|
|
188
|
+
try {
|
|
189
|
+
publicBaseUrl = getWebhookBaseUrl(loadConfig());
|
|
190
|
+
} catch {
|
|
191
|
+
// No webhook base URL configured — fall back to using req.url as-is
|
|
192
|
+
}
|
|
153
193
|
const parsedUrl = new URL(req.url);
|
|
154
194
|
const publicUrl = publicBaseUrl
|
|
155
|
-
? publicBaseUrl
|
|
195
|
+
? publicBaseUrl + parsedUrl.pathname + parsedUrl.search
|
|
156
196
|
: req.url;
|
|
157
197
|
|
|
158
198
|
const isValid = TwilioConversationRelayProvider.verifyWebhookSignature(
|
|
159
199
|
publicUrl,
|
|
160
200
|
params,
|
|
161
201
|
signature,
|
|
202
|
+
authToken,
|
|
162
203
|
);
|
|
163
204
|
|
|
164
205
|
if (!isValid) {
|
|
@@ -302,11 +343,18 @@ export class RuntimeHttpServer {
|
|
|
302
343
|
|
|
303
344
|
// ── Twilio webhook endpoints — before auth check because Twilio
|
|
304
345
|
// webhook POSTs don't include bearer tokens.
|
|
305
|
-
// Supports
|
|
346
|
+
// Supports /v1/calls/twilio/*, /v1/assistants/:id/calls/twilio/*,
|
|
347
|
+
// and gateway-compatible /webhooks/twilio/* paths.
|
|
306
348
|
// Validates X-Twilio-Signature to prevent unauthorized access. ──
|
|
307
349
|
const twilioMatch = path.match(TWILIO_WEBHOOK_RE);
|
|
308
|
-
|
|
309
|
-
|
|
350
|
+
const gatewayTwilioMatch = !twilioMatch ? path.match(TWILIO_GATEWAY_WEBHOOK_RE) : null;
|
|
351
|
+
const resolvedTwilioSubpath = twilioMatch
|
|
352
|
+
? twilioMatch[1]
|
|
353
|
+
: gatewayTwilioMatch
|
|
354
|
+
? GATEWAY_SUBPATH_MAP[gatewayTwilioMatch[1]]
|
|
355
|
+
: null;
|
|
356
|
+
if (resolvedTwilioSubpath && req.method === 'POST') {
|
|
357
|
+
const twilioSubpath = resolvedTwilioSubpath;
|
|
310
358
|
|
|
311
359
|
// Validate Twilio request signature before dispatching
|
|
312
360
|
const validation = await validateTwilioWebhook(req);
|
|
@@ -335,17 +383,6 @@ export class RuntimeHttpServer {
|
|
|
335
383
|
}
|
|
336
384
|
}
|
|
337
385
|
|
|
338
|
-
// ── Call answer endpoint — behind auth gate ──────────────────────
|
|
339
|
-
const callAnswerMatch = path.match(/^\/v1\/calls\/([^/]+)\/answer$/);
|
|
340
|
-
if (callAnswerMatch && req.method === 'POST') {
|
|
341
|
-
try {
|
|
342
|
-
return await handleCallAnswer(req, callAnswerMatch[1]);
|
|
343
|
-
} catch (err) {
|
|
344
|
-
log.error({ err, callSessionId: callAnswerMatch[1] }, 'Runtime HTTP handler error answering call');
|
|
345
|
-
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
386
|
// Serve shareable app pages
|
|
350
387
|
const pagesMatch = path.match(/^\/pages\/([^/]+)$/);
|
|
351
388
|
if (pagesMatch && req.method === 'GET') {
|
|
@@ -529,6 +566,68 @@ export class RuntimeHttpServer {
|
|
|
529
566
|
return await handleReplayDeadLetters(req);
|
|
530
567
|
}
|
|
531
568
|
|
|
569
|
+
// ── Call API routes ───────────────────────────────────────────
|
|
570
|
+
if (endpoint === 'calls/start' && req.method === 'POST') {
|
|
571
|
+
return await handleStartCall(req);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Match calls/:callSessionId and calls/:callSessionId/cancel, calls/:callSessionId/answer
|
|
575
|
+
const callsMatch = endpoint.match(/^calls\/([^/]+?)(\/cancel|\/answer)?$/);
|
|
576
|
+
if (callsMatch) {
|
|
577
|
+
const callSessionId = callsMatch[1];
|
|
578
|
+
// Skip known sub-paths that are handled elsewhere (twilio, relay)
|
|
579
|
+
if (callSessionId !== 'twilio' && callSessionId !== 'relay' && callSessionId !== 'start') {
|
|
580
|
+
if (callsMatch[2] === '/cancel' && req.method === 'POST') {
|
|
581
|
+
return await handleCancelCall(req, callSessionId);
|
|
582
|
+
}
|
|
583
|
+
if (callsMatch[2] === '/answer' && req.method === 'POST') {
|
|
584
|
+
return await handleAnswerCall(req, callSessionId);
|
|
585
|
+
}
|
|
586
|
+
if (!callsMatch[2] && req.method === 'GET') {
|
|
587
|
+
return handleGetCallStatus(callSessionId);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ── Internal Twilio forwarding endpoints (gateway → runtime) ──
|
|
593
|
+
// These accept JSON payloads from the gateway (which already validated
|
|
594
|
+
// the Twilio signature) and reconstruct requests for the existing
|
|
595
|
+
// Twilio route handlers.
|
|
596
|
+
if (endpoint === 'internal/twilio/voice-webhook' && req.method === 'POST') {
|
|
597
|
+
const json = await req.json() as { params: Record<string, string>; originalUrl?: string };
|
|
598
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
599
|
+
// Reconstruct request URL: keep the original URL query string (callSessionId)
|
|
600
|
+
const reconstructedUrl = json.originalUrl ?? req.url;
|
|
601
|
+
const fakeReq = new Request(reconstructedUrl, {
|
|
602
|
+
method: 'POST',
|
|
603
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
604
|
+
body: formBody,
|
|
605
|
+
});
|
|
606
|
+
return await handleVoiceWebhook(fakeReq);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (endpoint === 'internal/twilio/status' && req.method === 'POST') {
|
|
610
|
+
const json = await req.json() as { params: Record<string, string> };
|
|
611
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
612
|
+
const fakeReq = new Request(req.url, {
|
|
613
|
+
method: 'POST',
|
|
614
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
615
|
+
body: formBody,
|
|
616
|
+
});
|
|
617
|
+
return await handleStatusCallback(fakeReq);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (endpoint === 'internal/twilio/connect-action' && req.method === 'POST') {
|
|
621
|
+
const json = await req.json() as { params: Record<string, string> };
|
|
622
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
623
|
+
const fakeReq = new Request(req.url, {
|
|
624
|
+
method: 'POST',
|
|
625
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
626
|
+
body: formBody,
|
|
627
|
+
});
|
|
628
|
+
return await handleConnectAction(fakeReq);
|
|
629
|
+
}
|
|
630
|
+
|
|
532
631
|
return Response.json({ error: 'Not found', source: 'runtime' }, { status: 404 });
|
|
533
632
|
} catch (err) {
|
|
534
633
|
if (err instanceof IngressBlockedError) {
|
|
@@ -607,6 +706,18 @@ export class RuntimeHttpServer {
|
|
|
607
706
|
channelDeliveryStore.linkMessage(event.id, userMessageId);
|
|
608
707
|
channelDeliveryStore.markProcessed(event.id);
|
|
609
708
|
log.info({ eventId: event.id }, 'Successfully replayed failed channel event');
|
|
709
|
+
|
|
710
|
+
const replyCallbackUrl = typeof payload.replyCallbackUrl === 'string'
|
|
711
|
+
? payload.replyCallbackUrl
|
|
712
|
+
: undefined;
|
|
713
|
+
if (replyCallbackUrl) {
|
|
714
|
+
const externalChatId = typeof payload.externalChatId === 'string'
|
|
715
|
+
? payload.externalChatId
|
|
716
|
+
: undefined;
|
|
717
|
+
if (externalChatId) {
|
|
718
|
+
await this.deliverReplyViaCallback(event.conversationId, externalChatId, replyCallbackUrl);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
610
721
|
} catch (err) {
|
|
611
722
|
log.error({ err, eventId: event.id }, 'Retry failed for channel event');
|
|
612
723
|
channelDeliveryStore.recordProcessingFailure(event.id, err);
|
|
@@ -614,6 +725,39 @@ export class RuntimeHttpServer {
|
|
|
614
725
|
}
|
|
615
726
|
}
|
|
616
727
|
|
|
728
|
+
private async deliverReplyViaCallback(
|
|
729
|
+
conversationId: string,
|
|
730
|
+
externalChatId: string,
|
|
731
|
+
callbackUrl: string,
|
|
732
|
+
): Promise<void> {
|
|
733
|
+
const msgs = conversationStore.getMessages(conversationId);
|
|
734
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
735
|
+
if (msgs[i].role === 'assistant') {
|
|
736
|
+
let parsed: unknown;
|
|
737
|
+
try { parsed = JSON.parse(msgs[i].content); } catch { parsed = msgs[i].content; }
|
|
738
|
+
const rendered = renderHistoryContent(parsed);
|
|
739
|
+
|
|
740
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
|
|
741
|
+
const replyAttachments = linked.map((a) => ({
|
|
742
|
+
id: a.id,
|
|
743
|
+
filename: a.originalFilename,
|
|
744
|
+
mimeType: a.mimeType,
|
|
745
|
+
sizeBytes: a.sizeBytes,
|
|
746
|
+
kind: a.kind,
|
|
747
|
+
}));
|
|
748
|
+
|
|
749
|
+
if (rendered.text || replyAttachments.length > 0) {
|
|
750
|
+
await deliverChannelReply(callbackUrl, {
|
|
751
|
+
chatId: externalChatId,
|
|
752
|
+
text: rendered.text || undefined,
|
|
753
|
+
attachments: replyAttachments.length > 0 ? replyAttachments : undefined,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
617
761
|
private handleHealth(): Response {
|
|
618
762
|
return Response.json({
|
|
619
763
|
status: 'healthy',
|
|
@@ -56,7 +56,6 @@ export async function handleUploadAttachment(req: Request): Promise<Response> {
|
|
|
56
56
|
let attachment: attachmentsStore.StoredAttachment;
|
|
57
57
|
try {
|
|
58
58
|
attachment = attachmentsStore.uploadAttachment(
|
|
59
|
-
"self",
|
|
60
59
|
filename,
|
|
61
60
|
mimeType,
|
|
62
61
|
data,
|
|
@@ -98,7 +97,7 @@ export async function handleDeleteAttachment(req: Request): Promise<Response> {
|
|
|
98
97
|
);
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
const result = attachmentsStore.deleteAttachment(
|
|
100
|
+
const result = attachmentsStore.deleteAttachment(attachmentId);
|
|
102
101
|
|
|
103
102
|
if (result === 'not_found') {
|
|
104
103
|
return Response.json(
|
|
@@ -118,7 +117,7 @@ export async function handleDeleteAttachment(req: Request): Promise<Response> {
|
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
export function handleGetAttachment(attachmentId: string): Response {
|
|
121
|
-
const attachment = attachmentsStore.getAttachmentById(
|
|
120
|
+
const attachment = attachmentsStore.getAttachmentById(attachmentId);
|
|
122
121
|
if (!attachment) {
|
|
123
122
|
return Response.json({ error: 'Attachment not found' }, { status: 404 });
|
|
124
123
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime HTTP route handlers for the call API.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/calls/start — initiate a new call
|
|
5
|
+
* GET /v1/calls/:callSessionId — get call status
|
|
6
|
+
* POST /v1/calls/:callSessionId/cancel — cancel a call
|
|
7
|
+
* POST /v1/calls/:callSessionId/answer — answer a pending question
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { startCall, getCallStatus, cancelCall, answerCall } from '../../calls/call-domain.js';
|
|
11
|
+
import { getConfig } from '../../config/loader.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* POST /v1/calls/start
|
|
15
|
+
*
|
|
16
|
+
* Body: { phoneNumber: string; task: string; context?: string; conversationId: string }
|
|
17
|
+
*/
|
|
18
|
+
export async function handleStartCall(req: Request): Promise<Response> {
|
|
19
|
+
if (!getConfig().calls.enabled) {
|
|
20
|
+
return Response.json(
|
|
21
|
+
{ error: 'Calls feature is disabled via configuration. Set calls.enabled to true to use this feature.' },
|
|
22
|
+
{ status: 403 },
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let body: {
|
|
27
|
+
phoneNumber?: string;
|
|
28
|
+
task?: string;
|
|
29
|
+
context?: string;
|
|
30
|
+
conversationId?: string;
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
body = await req.json() as typeof body;
|
|
34
|
+
} catch {
|
|
35
|
+
return Response.json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!body.conversationId) {
|
|
39
|
+
return Response.json({ error: 'conversationId is required' }, { status: 400 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await startCall({
|
|
43
|
+
phoneNumber: body.phoneNumber ?? '',
|
|
44
|
+
task: body.task ?? '',
|
|
45
|
+
context: body.context,
|
|
46
|
+
conversationId: body.conversationId,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Response.json({
|
|
54
|
+
callSessionId: result.session.id,
|
|
55
|
+
callSid: result.callSid,
|
|
56
|
+
status: result.session.status,
|
|
57
|
+
toNumber: result.session.toNumber,
|
|
58
|
+
fromNumber: result.session.fromNumber,
|
|
59
|
+
}, { status: 201 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* GET /v1/calls/:callSessionId
|
|
64
|
+
*/
|
|
65
|
+
export function handleGetCallStatus(callSessionId: string): Response {
|
|
66
|
+
const result = getCallStatus(callSessionId);
|
|
67
|
+
|
|
68
|
+
if (!result.ok) {
|
|
69
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { session } = result;
|
|
73
|
+
return Response.json({
|
|
74
|
+
callSessionId: session.id,
|
|
75
|
+
conversationId: session.conversationId,
|
|
76
|
+
status: session.status,
|
|
77
|
+
toNumber: session.toNumber,
|
|
78
|
+
fromNumber: session.fromNumber,
|
|
79
|
+
provider: session.provider,
|
|
80
|
+
providerCallSid: session.providerCallSid,
|
|
81
|
+
task: session.task,
|
|
82
|
+
startedAt: session.startedAt ? new Date(session.startedAt).toISOString() : null,
|
|
83
|
+
endedAt: session.endedAt ? new Date(session.endedAt).toISOString() : null,
|
|
84
|
+
lastError: session.lastError,
|
|
85
|
+
pendingQuestion: result.pendingQuestion ?? null,
|
|
86
|
+
createdAt: new Date(session.createdAt).toISOString(),
|
|
87
|
+
updatedAt: new Date(session.updatedAt).toISOString(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* POST /v1/calls/:callSessionId/cancel
|
|
93
|
+
*
|
|
94
|
+
* Body: { reason?: string }
|
|
95
|
+
*/
|
|
96
|
+
export async function handleCancelCall(req: Request, callSessionId: string): Promise<Response> {
|
|
97
|
+
let reason: string | undefined;
|
|
98
|
+
try {
|
|
99
|
+
const body = await req.json() as { reason?: string };
|
|
100
|
+
reason = body.reason;
|
|
101
|
+
} catch {
|
|
102
|
+
// Empty body is fine
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result = await cancelCall({ callSessionId, reason });
|
|
106
|
+
|
|
107
|
+
if (!result.ok) {
|
|
108
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Response.json({
|
|
112
|
+
callSessionId: result.session.id,
|
|
113
|
+
status: result.session.status,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* POST /v1/calls/:callSessionId/answer
|
|
119
|
+
*
|
|
120
|
+
* Body: { answer: string }
|
|
121
|
+
*/
|
|
122
|
+
export async function handleAnswerCall(req: Request, callSessionId: string): Promise<Response> {
|
|
123
|
+
let body: { answer?: string };
|
|
124
|
+
try {
|
|
125
|
+
body = await req.json() as typeof body;
|
|
126
|
+
} catch {
|
|
127
|
+
return Response.json({ error: 'Invalid JSON in request body' }, { status: 400 });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = await answerCall({
|
|
131
|
+
callSessionId,
|
|
132
|
+
answer: body.answer ?? '',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!result.ok) {
|
|
136
|
+
return Response.json({ error: result.error }, { status: result.status ?? 500 });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Response.json({ ok: true, questionId: result.questionId });
|
|
140
|
+
}
|