@vellumai/assistant 0.3.4 → 0.3.6
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/Dockerfile +2 -0
- package/README.md +88 -2
- package/eslint.config.mjs +31 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
- package/scripts/ipc/generate-swift.ts +31 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
- package/src/__tests__/approval-conversation-turn.test.ts +214 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/browser-manager.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +130 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +799 -249
- package/src/__tests__/call-pointer-messages.test.ts +148 -0
- package/src/__tests__/call-recovery.test.ts +3 -0
- package/src/__tests__/call-routes-http.test.ts +32 -2
- package/src/__tests__/call-store.test.ts +3 -0
- package/src/__tests__/channel-approval-routes.test.ts +1277 -98
- package/src/__tests__/channel-approval.test.ts +37 -0
- package/src/__tests__/channel-approvals.test.ts +36 -50
- package/src/__tests__/channel-guardian.test.ts +630 -22
- package/src/__tests__/channel-readiness-service.test.ts +324 -0
- package/src/__tests__/checker.test.ts +14 -7
- package/src/__tests__/clarification-resolver.test.ts +44 -24
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
- package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
- package/src/__tests__/config-schema.test.ts +14 -8
- package/src/__tests__/context-window-manager.test.ts +30 -2
- package/src/__tests__/contradiction-checker.test.ts +20 -5
- package/src/__tests__/credential-security-invariants.test.ts +7 -2
- package/src/__tests__/daemon-lifecycle.test.ts +13 -12
- package/src/__tests__/db-migration-rollback.test.ts +752 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/fuzzy-match-property.test.ts +5 -5
- package/src/__tests__/guardian-action-store.test.ts +123 -0
- package/src/__tests__/guardian-action-sweep.test.ts +277 -0
- package/src/__tests__/guardian-dispatch.test.ts +389 -0
- package/src/__tests__/guardian-question-copy.test.ts +47 -0
- package/src/__tests__/handlers-telegram-config.test.ts +4 -2
- package/src/__tests__/handlers-twilio-config.test.ts +533 -0
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +291 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/model-intents.test.ts +96 -0
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
- package/src/__tests__/provider-error-scenarios.test.ts +621 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
- package/src/__tests__/qdrant-manager.test.ts +27 -20
- package/src/__tests__/relay-server.test.ts +779 -40
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
- package/src/__tests__/run-orchestrator.test.ts +42 -4
- package/src/__tests__/runtime-runs-http.test.ts +17 -1
- package/src/__tests__/runtime-runs.test.ts +16 -0
- package/src/__tests__/schedule-store.test.ts +18 -4
- package/src/__tests__/scheduler-recurrence.test.ts +13 -4
- package/src/__tests__/session-abort-tool-results.test.ts +6 -0
- package/src/__tests__/session-agent-loop.test.ts +857 -0
- package/src/__tests__/session-conflict-gate.test.ts +6 -0
- package/src/__tests__/session-pre-run-repair.test.ts +6 -0
- package/src/__tests__/session-profile-injection.test.ts +6 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/session-queue.test.ts +6 -0
- package/src/__tests__/session-runtime-assembly.test.ts +321 -13
- package/src/__tests__/session-slash-known.test.ts +6 -0
- package/src/__tests__/session-slash-queue.test.ts +6 -0
- package/src/__tests__/session-slash-unknown.test.ts +6 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/session-workspace-injection.test.ts +6 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/skills.test.ts +2 -0
- package/src/__tests__/sms-messaging-provider.test.ts +126 -0
- package/src/__tests__/starter-task-flow.test.ts +2 -0
- package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +2 -0
- package/src/__tests__/task-management-tools.test.ts +2 -2
- package/src/__tests__/task-runner.test.ts +14 -4
- package/src/__tests__/terminal-tools.test.ts +25 -19
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
- package/src/__tests__/tool-executor.test.ts +23 -24
- package/src/__tests__/trust-store.test.ts +3 -3
- package/src/__tests__/twilio-rest.test.ts +29 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
- package/src/__tests__/twilio-routes.test.ts +167 -11
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +2 -0
- package/src/__tests__/voice-quality.test.ts +222 -0
- package/src/__tests__/web-search.test.ts +46 -30
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/agent/loop.ts +1 -1
- package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
- package/src/amazon/client.ts +1418 -0
- package/src/amazon/request-extractor.ts +135 -0
- package/src/amazon/session.ts +109 -0
- package/src/autonomy/autonomy-store.ts +5 -5
- package/src/browser-extension-relay/client.ts +124 -0
- package/src/browser-extension-relay/protocol.ts +63 -0
- package/src/browser-extension-relay/server.ts +177 -0
- package/src/bundler/app-bundler.ts +3 -3
- package/src/bundler/bundle-signer.ts +1 -1
- package/src/bundler/signature-verifier.ts +1 -1
- package/src/calls/call-conversation-messages.ts +33 -0
- package/src/calls/call-domain.ts +114 -10
- package/src/calls/call-orchestrator.ts +268 -59
- package/src/calls/call-pointer-messages.ts +53 -0
- package/src/calls/call-recovery.ts +3 -8
- package/src/calls/call-store.ts +69 -87
- package/src/calls/elevenlabs-config.ts +3 -2
- package/src/calls/guardian-action-sweep.ts +105 -0
- package/src/calls/guardian-dispatch.ts +203 -0
- package/src/calls/guardian-question-copy.ts +133 -0
- package/src/calls/relay-server.ts +466 -8
- package/src/calls/speaker-identification.ts +1 -1
- package/src/calls/twilio-config.ts +22 -14
- package/src/calls/twilio-provider.ts +6 -4
- package/src/calls/twilio-rest.ts +308 -7
- package/src/calls/twilio-routes.ts +65 -12
- package/src/calls/types.ts +3 -1
- package/src/channels/types.ts +25 -0
- package/src/cli/amazon.ts +815 -0
- package/src/cli/config-commands.ts +2 -2
- package/src/cli/core-commands.ts +4 -3
- package/src/cli/influencer.ts +244 -0
- package/src/cli/map.ts +89 -6
- package/src/cli.ts +1 -1
- package/src/config/agent-schema.ts +171 -0
- package/src/config/bundled-skills/amazon/SKILL.md +127 -0
- package/src/config/bundled-skills/amazon/icon.svg +13 -0
- package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
- package/src/config/bundled-skills/browser/SKILL.md +1 -0
- package/src/config/bundled-skills/browser/TOOLS.json +17 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
- package/src/config/bundled-skills/doordash/SKILL.md +51 -51
- package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
- package/src/config/bundled-skills/influencer/SKILL.md +144 -0
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
- package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
- package/src/config/bundled-skills/messaging/SKILL.md +33 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/bundled-skills/twitter/icon.svg +14 -0
- package/src/config/bundled-tool-registry.ts +310 -0
- package/src/config/calls-schema.ts +181 -0
- package/src/config/core-schema.ts +309 -0
- package/src/config/defaults.ts +28 -3
- package/src/config/env-registry.ts +162 -0
- package/src/config/env.ts +175 -0
- package/src/config/loader.ts +6 -6
- package/src/config/memory-schema.ts +528 -0
- package/src/config/sandbox-schema.ts +55 -0
- package/src/config/schema.ts +158 -1133
- package/src/config/skill-state.ts +1 -1
- package/src/config/skills-schema.ts +32 -0
- package/src/config/skills.ts +35 -24
- package/src/config/system-prompt.ts +131 -56
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/user-reference.ts +4 -9
- package/src/config/vellum-skills/catalog.json +6 -7
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
- package/src/context/window-manager.ts +27 -7
- package/src/daemon/approval-generators.ts +186 -0
- package/src/daemon/approved-devices-store.ts +140 -0
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/classifier.ts +35 -32
- package/src/daemon/config-watcher.ts +1 -1
- package/src/daemon/daemon-control.ts +217 -0
- package/src/daemon/handlers/apps.ts +2 -3
- package/src/daemon/handlers/config-channels.ts +158 -0
- package/src/daemon/handlers/config-inbox.ts +540 -0
- package/src/daemon/handlers/config-ingress.ts +231 -0
- package/src/daemon/handlers/config-integrations.ts +258 -0
- package/src/daemon/handlers/config-model.ts +143 -0
- package/src/daemon/handlers/config-parental.ts +163 -0
- package/src/daemon/handlers/config-scheduling.ts +172 -0
- package/src/daemon/handlers/config-slack.ts +92 -0
- package/src/daemon/handlers/config-telegram.ts +301 -0
- package/src/daemon/handlers/config-tools.ts +177 -0
- package/src/daemon/handlers/config-trust.ts +104 -0
- package/src/daemon/handlers/config-twilio.ts +1080 -0
- package/src/daemon/handlers/config.ts +53 -1689
- package/src/daemon/handlers/diagnostics.ts +1 -1
- package/src/daemon/handlers/dictation.ts +180 -0
- package/src/daemon/handlers/documents.ts +18 -32
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +11 -0
- package/src/daemon/handlers/misc.ts +3 -5
- package/src/daemon/handlers/pairing.ts +98 -0
- package/src/daemon/handlers/sessions.ts +56 -5
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/handlers/twitter-auth.ts +2 -0
- package/src/daemon/handlers/work-items.ts +17 -9
- package/src/daemon/handlers/workspace-files.ts +4 -3
- package/src/daemon/install-cli-launchers.ts +113 -0
- package/src/daemon/ipc-contract/apps.ts +356 -0
- package/src/daemon/ipc-contract/browser.ts +74 -0
- package/src/daemon/ipc-contract/computer-use.ts +151 -0
- package/src/daemon/ipc-contract/diagnostics.ts +56 -0
- package/src/daemon/ipc-contract/documents.ts +74 -0
- package/src/daemon/ipc-contract/inbox.ts +209 -0
- package/src/daemon/ipc-contract/integrations.ts +284 -0
- package/src/daemon/ipc-contract/memory.ts +48 -0
- package/src/daemon/ipc-contract/messages.ts +211 -0
- package/src/daemon/ipc-contract/pairing.ts +45 -0
- package/src/daemon/ipc-contract/parental-control.ts +95 -0
- package/src/daemon/ipc-contract/schedules.ts +97 -0
- package/src/daemon/ipc-contract/sessions.ts +315 -0
- package/src/daemon/ipc-contract/shared.ts +42 -0
- package/src/daemon/ipc-contract/skills.ts +120 -0
- package/src/daemon/ipc-contract/subagents.ts +58 -0
- package/src/daemon/ipc-contract/surfaces.ts +250 -0
- package/src/daemon/ipc-contract/trust.ts +60 -0
- package/src/daemon/ipc-contract/work-items.ts +225 -0
- package/src/daemon/ipc-contract/workspace.ts +113 -0
- package/src/daemon/ipc-contract-inventory.json +70 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +229 -2426
- package/src/daemon/ipc-protocol.ts +1 -1
- package/src/daemon/ipc-validate.ts +7 -0
- package/src/daemon/lifecycle.ts +97 -377
- package/src/daemon/pairing-store.ts +177 -0
- package/src/daemon/providers-setup.ts +43 -0
- package/src/daemon/ride-shotgun-handler.ts +68 -3
- package/src/daemon/server.ts +66 -46
- package/src/daemon/session-agent-loop-handlers.ts +421 -0
- package/src/daemon/session-agent-loop.ts +117 -275
- package/src/daemon/session-dynamic-profile.ts +1 -1
- package/src/daemon/session-history.ts +1 -1
- package/src/daemon/session-media-retry.ts +1 -1
- package/src/daemon/session-messaging.ts +37 -2
- package/src/daemon/session-notifiers.ts +5 -25
- package/src/daemon/session-process.ts +99 -59
- package/src/daemon/session-queue-manager.ts +96 -4
- package/src/daemon/session-runtime-assembly.ts +199 -10
- package/src/daemon/session-surfaces.ts +19 -4
- package/src/daemon/session-tool-setup.ts +30 -30
- package/src/daemon/session-workspace.ts +1 -1
- package/src/daemon/session.ts +35 -2
- package/src/daemon/shutdown-handlers.ts +122 -0
- package/src/daemon/trace-emitter.ts +1 -1
- package/src/daemon/watch-handler.ts +36 -33
- package/src/doordash/cart-queries.ts +787 -0
- package/src/doordash/client.ts +144 -127
- package/src/doordash/order-queries.ts +85 -0
- package/src/doordash/queries.ts +10 -1308
- package/src/doordash/search-queries.ts +203 -0
- package/src/doordash/session.ts +3 -2
- package/src/doordash/store-queries.ts +246 -0
- package/src/doordash/types.ts +367 -0
- package/src/email/providers/agentmail.ts +2 -1
- package/src/email/providers/index.ts +3 -2
- package/src/email/service.ts +3 -2
- package/src/errors.ts +43 -0
- package/src/home-base/prebuilt/seed.ts +1 -1
- package/src/hooks/cli.ts +6 -5
- package/src/hooks/config.ts +6 -8
- package/src/hooks/discovery.ts +6 -5
- package/src/hooks/manager.ts +4 -3
- package/src/hooks/runner.ts +2 -2
- package/src/hooks/templates.ts +5 -5
- package/src/inbound/public-ingress-urls.ts +6 -4
- package/src/index.ts +4 -2
- package/src/influencer/client.ts +1104 -0
- package/src/instrument.ts +4 -3
- package/src/logfire.ts +4 -3
- package/src/memory/admin.ts +25 -35
- package/src/memory/attachments-store.ts +4 -7
- package/src/memory/channel-delivery-store.ts +30 -1
- package/src/memory/channel-guardian-store.ts +202 -2
- package/src/memory/clarification-resolver.ts +37 -33
- package/src/memory/conflict-store.ts +67 -61
- package/src/memory/contradiction-checker.ts +141 -117
- package/src/memory/conversation-store.ts +335 -51
- package/src/memory/db-connection.ts +27 -4
- package/src/memory/db-init.ts +265 -4
- package/src/memory/db.ts +14 -1
- package/src/memory/embedding-backend.ts +27 -5
- package/src/memory/embedding-ollama.ts +2 -1
- package/src/memory/entity-extractor.ts +38 -35
- package/src/memory/guardian-action-store.ts +430 -0
- package/src/memory/inbox-escalation-projection.ts +59 -0
- package/src/memory/inbox-thread-store.ts +218 -0
- package/src/memory/ingress-invite-store.ts +338 -0
- package/src/memory/ingress-member-store.ts +350 -0
- package/src/memory/items-extractor.ts +91 -97
- package/src/memory/job-handlers/index-maintenance.ts +3 -3
- package/src/memory/job-handlers/media-processing.ts +69 -0
- package/src/memory/job-handlers/summarization.ts +32 -26
- package/src/memory/job-utils.ts +3 -10
- package/src/memory/jobs-store.ts +8 -10
- package/src/memory/jobs-worker.ts +55 -36
- package/src/memory/media-store.ts +759 -0
- package/src/memory/migrations/001-job-deferrals.ts +45 -0
- package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
- package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
- package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
- package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
- package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
- package/src/memory/migrations/index.ts +24 -0
- package/src/memory/migrations/registry.ts +79 -0
- package/src/memory/migrations/validate-migration-state.ts +69 -0
- package/src/memory/qdrant-manager.ts +49 -8
- package/src/memory/query-builder.ts +1 -1
- package/src/memory/raw-query.ts +119 -0
- package/src/memory/recall-cache.ts +4 -1
- package/src/memory/retriever.ts +165 -47
- package/src/memory/schema-migration.ts +25 -984
- package/src/memory/schema.ts +228 -7
- package/src/memory/search/entity.ts +205 -31
- package/src/memory/search/lexical.ts +81 -52
- package/src/memory/search/ranking.ts +27 -23
- package/src/memory/search/semantic.ts +157 -19
- package/src/memory/search/types.ts +24 -0
- package/src/memory/shared-app-links-store.ts +4 -5
- package/src/memory/validation.ts +19 -0
- package/src/messaging/draft-store.ts +5 -6
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +201 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
- package/src/messaging/providers/whatsapp/adapter.ts +136 -0
- package/src/messaging/providers/whatsapp/client.ts +67 -0
- package/src/messaging/style-analyzer.ts +5 -4
- package/src/messaging/thread-summarizer.ts +61 -69
- package/src/messaging/triage-engine.ts +62 -71
- package/src/migrations/config-merge.ts +53 -0
- package/src/migrations/data-layout.ts +68 -0
- package/src/migrations/data-merge.ts +33 -0
- package/src/migrations/hooks-merge.ts +90 -0
- package/src/migrations/index.ts +6 -0
- package/src/migrations/log.ts +23 -0
- package/src/migrations/skills-merge.ts +33 -0
- package/src/migrations/workspace-layout.ts +79 -0
- package/src/permissions/checker.ts +133 -11
- package/src/permissions/prompter.ts +14 -0
- package/src/permissions/shell-identity.ts +31 -1
- package/src/permissions/trust-store.ts +21 -1
- package/src/providers/anthropic/client.ts +4 -4
- package/src/providers/failover.ts +2 -2
- package/src/providers/model-intents.ts +70 -0
- package/src/providers/ollama/client.ts +2 -1
- package/src/providers/provider-send-message.ts +176 -0
- package/src/providers/registry.ts +71 -30
- package/src/providers/retry.ts +35 -1
- package/src/providers/types.ts +12 -1
- package/src/runtime/approval-conversation-turn.ts +97 -0
- package/src/runtime/approval-message-composer.ts +253 -0
- package/src/runtime/channel-approval-parser.ts +36 -2
- package/src/runtime/channel-approvals.ts +11 -24
- package/src/runtime/channel-guardian-service.ts +88 -21
- package/src/runtime/channel-readiness-service.ts +418 -0
- package/src/runtime/channel-readiness-types.ts +35 -0
- package/src/runtime/channel-retry-sweep.ts +184 -0
- package/src/runtime/guardian-context-resolver.ts +108 -0
- package/src/runtime/http-server.ts +275 -717
- package/src/runtime/http-types.ts +59 -3
- package/src/runtime/middleware/auth.ts +116 -0
- package/src/runtime/middleware/error-handler.ts +33 -0
- package/src/runtime/middleware/twilio-validation.ts +127 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +51 -7
- package/src/runtime/routes/channel-delivery-routes.ts +170 -0
- package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
- package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
- package/src/runtime/routes/channel-route-shared.ts +144 -0
- package/src/runtime/routes/channel-routes.ts +32 -1588
- package/src/runtime/routes/conversation-routes.ts +50 -7
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +126 -0
- package/src/runtime/routes/pairing-routes.ts +143 -0
- package/src/runtime/routes/run-routes.ts +15 -1
- package/src/runtime/run-orchestrator.ts +86 -35
- package/src/schedule/schedule-store.ts +36 -32
- package/src/schedule/scheduler.ts +3 -3
- package/src/security/encrypted-store.ts +5 -7
- package/src/security/oauth2.ts +45 -15
- package/src/security/parental-control-store.ts +183 -0
- package/src/security/secret-allowlist.ts +4 -3
- package/src/security/secret-scanner.ts +5 -5
- package/src/security/secure-keys.ts +1 -1
- package/src/security/token-manager.ts +3 -2
- package/src/services/vercel-deploy.ts +6 -2
- package/src/skills/tool-manifest.ts +3 -3
- package/src/skills/vellum-catalog-remote.ts +75 -16
- package/src/slack/slack-webhook.ts +2 -1
- package/src/swarm/orchestrator.ts +92 -1
- package/src/swarm/router-planner.ts +6 -9
- package/src/swarm/worker-prompts.ts +9 -12
- package/src/tasks/task-compiler.ts +19 -28
- package/src/tasks/task-runner.ts +1 -1
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/assets/search.ts +15 -14
- package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
- package/src/tools/browser/auto-navigate.ts +1 -0
- package/src/tools/browser/browser-execution.ts +10 -1
- package/src/tools/browser/browser-manager.ts +119 -4
- package/src/tools/browser/network-recorder.ts +5 -0
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/broker.ts +11 -2
- package/src/tools/credentials/metadata-store.ts +18 -14
- package/src/tools/credentials/post-connect-hooks.ts +61 -0
- package/src/tools/credentials/vault.ts +49 -23
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +68 -9
- package/src/tools/host-terminal/cli-discover.ts +1 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
- package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
- package/src/tools/network/script-proxy/server.ts +1 -1
- package/src/tools/network/script-proxy/session-manager.ts +6 -5
- package/src/tools/network/web-fetch.ts +18 -2
- package/src/tools/network/web-search.ts +8 -4
- package/src/tools/reminder/reminder-store.ts +14 -15
- package/src/tools/schedule/create.ts +1 -0
- package/src/tools/schedule/list.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
- package/src/tools/skills/skill-script-runner.ts +24 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/tasks/work-item-enqueue.ts +2 -2
- package/src/tools/terminal/evaluate-typescript.ts +21 -12
- package/src/tools/terminal/parser.ts +50 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/watcher/delete.ts +6 -0
- package/src/tools/weather/service.ts +1 -1
- package/src/twitter/client.ts +190 -24
- package/src/twitter/router.ts +1 -1
- package/src/twitter/session.ts +4 -3
- package/src/util/clipboard.ts +1 -1
- package/src/util/errors.ts +65 -8
- package/src/util/fs.ts +40 -0
- package/src/util/json.ts +10 -0
- package/src/util/log-redact.ts +189 -0
- package/src/util/logger.ts +19 -17
- package/src/util/object.ts +3 -0
- package/src/util/platform.ts +105 -363
- package/src/util/pricing.ts +1 -1
- package/src/util/promise-guard.ts +1 -1
- package/src/util/retry.ts +19 -0
- package/src/util/row-mapper.ts +79 -0
- package/src/util/silently.ts +21 -0
- package/src/watcher/engine.ts +5 -1
- package/src/watcher/provider-types.ts +20 -0
- package/src/watcher/providers/github.ts +156 -0
- package/src/watcher/providers/gmail.ts +1 -0
- package/src/watcher/providers/google-calendar.ts +1 -0
- package/src/watcher/providers/linear.ts +460 -0
- package/src/watcher/providers/slack.ts +1 -0
- package/src/work-items/work-item-runner.ts +1 -1
- package/src/workspace/git-service.ts +1 -1
- package/src/workspace/provider-commit-message-generator.ts +51 -22
- package/src/__tests__/call-bridge.test.ts +0 -517
- package/src/__tests__/session-process-bridge.test.ts +0 -244
- package/src/calls/call-bridge.ts +0 -168
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChannelId,
|
|
3
|
+
ChannelProbe,
|
|
4
|
+
ChannelProbeContext,
|
|
5
|
+
ChannelReadinessSnapshot,
|
|
6
|
+
ReadinessCheckResult,
|
|
7
|
+
} from './channel-readiness-types.js';
|
|
8
|
+
import {
|
|
9
|
+
hasTwilioCredentials,
|
|
10
|
+
getTollFreeVerificationStatus,
|
|
11
|
+
getPhoneNumberSid,
|
|
12
|
+
} from '../calls/twilio-rest.js';
|
|
13
|
+
import { getSecureKey } from '../security/secure-keys.js';
|
|
14
|
+
import { loadRawConfig } from '../config/loader.js';
|
|
15
|
+
import { getTwilioPhoneNumberEnv } from '../config/env.js';
|
|
16
|
+
|
|
17
|
+
/** Remote check results are cached for 5 minutes before being considered stale. */
|
|
18
|
+
export const REMOTE_TTL_MS = 5 * 60 * 1000;
|
|
19
|
+
|
|
20
|
+
// ── SMS Probe ───────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
function hasIngressConfigured(): boolean {
|
|
23
|
+
try {
|
|
24
|
+
const raw = loadRawConfig();
|
|
25
|
+
const ingress = (raw?.ingress ?? {}) as Record<string, unknown>;
|
|
26
|
+
const publicBaseUrl = (ingress.publicBaseUrl as string) ?? '';
|
|
27
|
+
const enabled = (ingress.enabled as boolean | undefined) ?? (publicBaseUrl ? true : false);
|
|
28
|
+
return enabled && publicBaseUrl.length > 0;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getAssistantMappedPhoneNumber(
|
|
35
|
+
smsConfig: Record<string, unknown>,
|
|
36
|
+
assistantId?: string,
|
|
37
|
+
): string | undefined {
|
|
38
|
+
if (!assistantId) return undefined;
|
|
39
|
+
const mapping = (smsConfig.assistantPhoneNumbers as Record<string, string> | undefined) ?? {};
|
|
40
|
+
return mapping[assistantId];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function hasAnyAssistantMappedPhoneNumber(smsConfig: Record<string, unknown>): boolean {
|
|
44
|
+
const mapping = (smsConfig.assistantPhoneNumbers as Record<string, string> | undefined) ?? {};
|
|
45
|
+
return Object.keys(mapping).length > 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasAnyAssistantMappedPhoneNumberSafe(): boolean {
|
|
49
|
+
try {
|
|
50
|
+
const raw = loadRawConfig();
|
|
51
|
+
const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
|
|
52
|
+
return hasAnyAssistantMappedPhoneNumber(smsConfig);
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve SMS from-number with canonical precedence:
|
|
60
|
+
* assistant mapping -> env override -> config sms.phoneNumber -> secure key fallback.
|
|
61
|
+
*/
|
|
62
|
+
function resolveSmsPhoneNumber(assistantId?: string): string {
|
|
63
|
+
try {
|
|
64
|
+
const raw = loadRawConfig();
|
|
65
|
+
const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
|
|
66
|
+
const mapped = getAssistantMappedPhoneNumber(smsConfig, assistantId);
|
|
67
|
+
return mapped
|
|
68
|
+
|| getTwilioPhoneNumberEnv()
|
|
69
|
+
|| (smsConfig.phoneNumber as string)
|
|
70
|
+
|| getSecureKey('credential:twilio:phone_number')
|
|
71
|
+
|| '';
|
|
72
|
+
} catch {
|
|
73
|
+
return getTwilioPhoneNumberEnv()
|
|
74
|
+
|| getSecureKey('credential:twilio:phone_number')
|
|
75
|
+
|| '';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const smsProbe: ChannelProbe = {
|
|
80
|
+
channel: 'sms',
|
|
81
|
+
runLocalChecks(context?: ChannelProbeContext): ReadinessCheckResult[] {
|
|
82
|
+
const results: ReadinessCheckResult[] = [];
|
|
83
|
+
|
|
84
|
+
const hasCreds = hasTwilioCredentials();
|
|
85
|
+
results.push({
|
|
86
|
+
name: 'twilio_credentials',
|
|
87
|
+
passed: hasCreds,
|
|
88
|
+
message: hasCreds
|
|
89
|
+
? 'Twilio credentials are configured'
|
|
90
|
+
: 'Twilio Account SID and Auth Token are not configured',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const resolvedNumber = resolveSmsPhoneNumber(context?.assistantId);
|
|
94
|
+
const hasPhone = !!resolvedNumber || (!context?.assistantId && hasAnyAssistantMappedPhoneNumberSafe());
|
|
95
|
+
results.push({
|
|
96
|
+
name: 'phone_number',
|
|
97
|
+
passed: hasPhone,
|
|
98
|
+
message: hasPhone
|
|
99
|
+
? (context?.assistantId && !resolvedNumber
|
|
100
|
+
? `Assistant ${context.assistantId} has no direct mapping, but SMS phone numbers are assigned`
|
|
101
|
+
: 'Phone number is assigned')
|
|
102
|
+
: (context?.assistantId
|
|
103
|
+
? `No phone number assigned for assistant ${context.assistantId}`
|
|
104
|
+
: 'No phone number assigned'),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const hasIngress = hasIngressConfigured();
|
|
108
|
+
results.push({
|
|
109
|
+
name: 'ingress',
|
|
110
|
+
passed: hasIngress,
|
|
111
|
+
message: hasIngress
|
|
112
|
+
? 'Public ingress URL is configured'
|
|
113
|
+
: 'Public ingress URL is not configured or disabled',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return results;
|
|
117
|
+
},
|
|
118
|
+
async runRemoteChecks(context?: ChannelProbeContext): Promise<ReadinessCheckResult[]> {
|
|
119
|
+
if (!hasTwilioCredentials()) return [];
|
|
120
|
+
|
|
121
|
+
const accountSid = getSecureKey('credential:twilio:account_sid');
|
|
122
|
+
const authToken = getSecureKey('credential:twilio:auth_token');
|
|
123
|
+
if (!accountSid || !authToken) return [];
|
|
124
|
+
|
|
125
|
+
const phoneNumber = resolveSmsPhoneNumber(context?.assistantId);
|
|
126
|
+
if (!phoneNumber) return [];
|
|
127
|
+
|
|
128
|
+
// Only toll-free numbers need verification checks
|
|
129
|
+
const tollFreePrefixes = ['+1800', '+1833', '+1844', '+1855', '+1866', '+1877', '+1888'];
|
|
130
|
+
const isTollFree = tollFreePrefixes.some((prefix) => phoneNumber.startsWith(prefix));
|
|
131
|
+
if (!isTollFree) return [];
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const phoneSid = await getPhoneNumberSid(accountSid, authToken, phoneNumber);
|
|
135
|
+
if (!phoneSid) {
|
|
136
|
+
return [{
|
|
137
|
+
name: 'toll_free_verification',
|
|
138
|
+
passed: false,
|
|
139
|
+
message: `Phone number ${phoneNumber} not found on Twilio account`,
|
|
140
|
+
}];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const verification = await getTollFreeVerificationStatus(accountSid, authToken, phoneSid);
|
|
144
|
+
if (!verification) {
|
|
145
|
+
return [{
|
|
146
|
+
name: 'toll_free_verification',
|
|
147
|
+
passed: false,
|
|
148
|
+
message: 'No toll-free verification submitted. Verification is required for SMS sending.',
|
|
149
|
+
}];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const approved = verification.status === 'TWILIO_APPROVED';
|
|
153
|
+
return [{
|
|
154
|
+
name: 'toll_free_verification',
|
|
155
|
+
passed: approved,
|
|
156
|
+
message: `toll_free_verification: ${verification.status}`,
|
|
157
|
+
}];
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
160
|
+
return [{
|
|
161
|
+
name: 'toll_free_verification',
|
|
162
|
+
passed: false,
|
|
163
|
+
message: `Failed to check toll-free verification: ${message}`,
|
|
164
|
+
}];
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// ── Voice Probe ─────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resolve voice from-number with the same precedence as SMS:
|
|
173
|
+
* assistant mapping -> env override -> config sms.phoneNumber -> secure key fallback.
|
|
174
|
+
*
|
|
175
|
+
* Voice and SMS share the same Twilio phone number infrastructure, so the
|
|
176
|
+
* resolution logic is identical to resolveSmsPhoneNumber.
|
|
177
|
+
*/
|
|
178
|
+
function resolveVoicePhoneNumber(assistantId?: string): string {
|
|
179
|
+
try {
|
|
180
|
+
const raw = loadRawConfig();
|
|
181
|
+
const smsConfig = (raw?.sms ?? {}) as Record<string, unknown>;
|
|
182
|
+
const mapped = getAssistantMappedPhoneNumber(smsConfig, assistantId);
|
|
183
|
+
return mapped
|
|
184
|
+
|| getTwilioPhoneNumberEnv()
|
|
185
|
+
|| (smsConfig.phoneNumber as string)
|
|
186
|
+
|| getSecureKey('credential:twilio:phone_number')
|
|
187
|
+
|| '';
|
|
188
|
+
} catch {
|
|
189
|
+
return getTwilioPhoneNumberEnv()
|
|
190
|
+
|| getSecureKey('credential:twilio:phone_number')
|
|
191
|
+
|| '';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const voiceProbe: ChannelProbe = {
|
|
196
|
+
channel: 'voice',
|
|
197
|
+
runLocalChecks(context?: ChannelProbeContext): ReadinessCheckResult[] {
|
|
198
|
+
const results: ReadinessCheckResult[] = [];
|
|
199
|
+
|
|
200
|
+
const hasCreds = hasTwilioCredentials();
|
|
201
|
+
results.push({
|
|
202
|
+
name: 'twilio_credentials',
|
|
203
|
+
passed: hasCreds,
|
|
204
|
+
message: hasCreds
|
|
205
|
+
? 'Twilio credentials are configured'
|
|
206
|
+
: 'Twilio Account SID and Auth Token are not configured',
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const resolvedNumber = resolveVoicePhoneNumber(context?.assistantId);
|
|
210
|
+
const hasPhone = !!resolvedNumber || (!context?.assistantId && hasAnyAssistantMappedPhoneNumberSafe());
|
|
211
|
+
results.push({
|
|
212
|
+
name: 'phone_number',
|
|
213
|
+
passed: hasPhone,
|
|
214
|
+
message: hasPhone
|
|
215
|
+
? (context?.assistantId && !resolvedNumber
|
|
216
|
+
? `Assistant ${context.assistantId} has no direct mapping, but phone numbers are assigned`
|
|
217
|
+
: 'Phone number is assigned for voice calls')
|
|
218
|
+
: (context?.assistantId
|
|
219
|
+
? `No phone number assigned for assistant ${context.assistantId}`
|
|
220
|
+
: 'No phone number assigned for voice calls'),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const hasIngress = hasIngressConfigured();
|
|
224
|
+
results.push({
|
|
225
|
+
name: 'ingress',
|
|
226
|
+
passed: hasIngress,
|
|
227
|
+
message: hasIngress
|
|
228
|
+
? 'Public ingress URL is configured'
|
|
229
|
+
: 'Public ingress URL is not configured or disabled',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return results;
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// ── Telegram Probe ──────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
const telegramProbe: ChannelProbe = {
|
|
239
|
+
channel: 'telegram',
|
|
240
|
+
runLocalChecks(): ReadinessCheckResult[] {
|
|
241
|
+
const results: ReadinessCheckResult[] = [];
|
|
242
|
+
|
|
243
|
+
const hasBotToken = !!getSecureKey('credential:telegram:bot_token');
|
|
244
|
+
results.push({
|
|
245
|
+
name: 'bot_token',
|
|
246
|
+
passed: hasBotToken,
|
|
247
|
+
message: hasBotToken
|
|
248
|
+
? 'Telegram bot token is configured'
|
|
249
|
+
: 'Telegram bot token is not configured',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const hasWebhookSecret = !!getSecureKey('credential:telegram:webhook_secret');
|
|
253
|
+
results.push({
|
|
254
|
+
name: 'webhook_secret',
|
|
255
|
+
passed: hasWebhookSecret,
|
|
256
|
+
message: hasWebhookSecret
|
|
257
|
+
? 'Telegram webhook secret is configured'
|
|
258
|
+
: 'Telegram webhook secret is not configured',
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const hasIngress = hasIngressConfigured();
|
|
262
|
+
results.push({
|
|
263
|
+
name: 'ingress',
|
|
264
|
+
passed: hasIngress,
|
|
265
|
+
message: hasIngress
|
|
266
|
+
? 'Public ingress URL is configured'
|
|
267
|
+
: 'Public ingress URL is not configured or disabled',
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return results;
|
|
271
|
+
},
|
|
272
|
+
// Telegram has no remote checks currently
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// ── Service ─────────────────────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
export class ChannelReadinessService {
|
|
278
|
+
private probes = new Map<ChannelId, ChannelProbe>();
|
|
279
|
+
private snapshots = new Map<string, ChannelReadinessSnapshot>();
|
|
280
|
+
|
|
281
|
+
registerProbe(probe: ChannelProbe): void {
|
|
282
|
+
this.probes.set(probe.channel, probe);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get readiness snapshots for the specified channel (or all registered channels).
|
|
287
|
+
* Local checks always run inline. Remote checks run only when `includeRemote`
|
|
288
|
+
* is true and the cache is stale or missing.
|
|
289
|
+
*/
|
|
290
|
+
async getReadiness(
|
|
291
|
+
channel?: ChannelId,
|
|
292
|
+
includeRemote?: boolean,
|
|
293
|
+
assistantId?: string,
|
|
294
|
+
): Promise<ChannelReadinessSnapshot[]> {
|
|
295
|
+
const channels = channel
|
|
296
|
+
? [channel]
|
|
297
|
+
: Array.from(this.probes.keys());
|
|
298
|
+
|
|
299
|
+
const results: ChannelReadinessSnapshot[] = [];
|
|
300
|
+
for (const ch of channels) {
|
|
301
|
+
const probe = this.probes.get(ch);
|
|
302
|
+
if (!probe) {
|
|
303
|
+
results.push(this.unsupportedSnapshot(ch));
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const probeContext: ChannelProbeContext = { assistantId };
|
|
308
|
+
const localChecks = probe.runLocalChecks(probeContext);
|
|
309
|
+
let remoteChecks: ReadinessCheckResult[] | undefined;
|
|
310
|
+
let remoteChecksFreshlyFetched = false;
|
|
311
|
+
let remoteChecksAffectReadiness = false;
|
|
312
|
+
let stale = false;
|
|
313
|
+
|
|
314
|
+
const cacheKey = this.snapshotCacheKey(ch, assistantId);
|
|
315
|
+
const cached = this.snapshots.get(cacheKey);
|
|
316
|
+
const now = Date.now();
|
|
317
|
+
|
|
318
|
+
if (includeRemote && probe.runRemoteChecks) {
|
|
319
|
+
const cacheExpired = !cached || !cached.remoteChecks || (now - cached.checkedAt) >= REMOTE_TTL_MS;
|
|
320
|
+
if (cacheExpired) {
|
|
321
|
+
remoteChecks = await probe.runRemoteChecks(probeContext);
|
|
322
|
+
remoteChecksFreshlyFetched = true;
|
|
323
|
+
remoteChecksAffectReadiness = true;
|
|
324
|
+
} else {
|
|
325
|
+
// Reuse cached remote checks
|
|
326
|
+
remoteChecks = cached.remoteChecks;
|
|
327
|
+
remoteChecksAffectReadiness = true;
|
|
328
|
+
}
|
|
329
|
+
} else if (cached?.remoteChecks) {
|
|
330
|
+
// Surface cached remote checks for visibility but never let them affect
|
|
331
|
+
// readiness when the caller explicitly opted out of remote checks.
|
|
332
|
+
remoteChecks = cached.remoteChecks;
|
|
333
|
+
stale = (now - cached.checkedAt) >= REMOTE_TTL_MS;
|
|
334
|
+
remoteChecksAffectReadiness = false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const allLocalPassed = localChecks.every((c) => c.passed);
|
|
338
|
+
const allRemotePassed = (remoteChecks && remoteChecksAffectReadiness)
|
|
339
|
+
? remoteChecks.every((c) => c.passed)
|
|
340
|
+
: true;
|
|
341
|
+
const ready = allLocalPassed && allRemotePassed;
|
|
342
|
+
|
|
343
|
+
const reasons: Array<{ code: string; text: string }> = [];
|
|
344
|
+
for (const check of localChecks) {
|
|
345
|
+
if (!check.passed) {
|
|
346
|
+
reasons.push({ code: check.name, text: check.message });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (remoteChecks && remoteChecksAffectReadiness) {
|
|
350
|
+
for (const check of remoteChecks) {
|
|
351
|
+
if (!check.passed) {
|
|
352
|
+
reasons.push({ code: check.name, text: check.message });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const snapshot: ChannelReadinessSnapshot = {
|
|
358
|
+
channel: ch,
|
|
359
|
+
ready,
|
|
360
|
+
checkedAt: (remoteChecks && cached && !remoteChecksFreshlyFetched) ? cached.checkedAt : now,
|
|
361
|
+
stale,
|
|
362
|
+
reasons,
|
|
363
|
+
localChecks,
|
|
364
|
+
remoteChecks,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
this.snapshots.set(cacheKey, snapshot);
|
|
368
|
+
results.push(snapshot);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return results;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Clear cached snapshot for a specific channel, forcing re-evaluation on next call. */
|
|
375
|
+
invalidateChannel(channel: ChannelId, assistantId?: string): void {
|
|
376
|
+
if (assistantId) {
|
|
377
|
+
this.snapshots.delete(this.snapshotCacheKey(channel, assistantId));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const prefix = `${channel}::`;
|
|
381
|
+
for (const key of this.snapshots.keys()) {
|
|
382
|
+
if (key.startsWith(prefix)) {
|
|
383
|
+
this.snapshots.delete(key);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Clear all cached snapshots. */
|
|
389
|
+
invalidateAll(): void {
|
|
390
|
+
this.snapshots.clear();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private unsupportedSnapshot(channel: ChannelId): ChannelReadinessSnapshot {
|
|
394
|
+
return {
|
|
395
|
+
channel,
|
|
396
|
+
ready: false,
|
|
397
|
+
checkedAt: Date.now(),
|
|
398
|
+
stale: false,
|
|
399
|
+
reasons: [{ code: 'unsupported_channel', text: `Channel ${channel} is not supported` }],
|
|
400
|
+
localChecks: [],
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private snapshotCacheKey(channel: ChannelId, assistantId?: string): string {
|
|
405
|
+
return `${channel}::${assistantId ?? '__default__'}`;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ── Factory ─────────────────────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
/** Create a service instance with built-in SMS, Voice, and Telegram probes registered. */
|
|
412
|
+
export function createReadinessService(): ChannelReadinessService {
|
|
413
|
+
const service = new ChannelReadinessService();
|
|
414
|
+
service.registerProbe(smsProbe);
|
|
415
|
+
service.registerProbe(voiceProbe);
|
|
416
|
+
service.registerProbe(telegramProbe);
|
|
417
|
+
return service;
|
|
418
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Channel readiness types — reusable primitive for all channels.
|
|
2
|
+
|
|
3
|
+
import type { ChannelId } from '../channels/types.js';
|
|
4
|
+
|
|
5
|
+
export type { ChannelId };
|
|
6
|
+
|
|
7
|
+
/** Result of a single readiness check (local or remote). */
|
|
8
|
+
export interface ReadinessCheckResult {
|
|
9
|
+
name: string;
|
|
10
|
+
passed: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Point-in-time snapshot of a channel's readiness state. */
|
|
15
|
+
export interface ChannelReadinessSnapshot {
|
|
16
|
+
channel: ChannelId;
|
|
17
|
+
ready: boolean;
|
|
18
|
+
checkedAt: number;
|
|
19
|
+
stale: boolean;
|
|
20
|
+
reasons: Array<{ code: string; text: string }>;
|
|
21
|
+
localChecks: ReadinessCheckResult[];
|
|
22
|
+
remoteChecks?: ReadinessCheckResult[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Optional probe context for assistant-scoped readiness checks. */
|
|
26
|
+
export interface ChannelProbeContext {
|
|
27
|
+
assistantId?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Probe interface that channels implement to provide readiness checks. */
|
|
31
|
+
export interface ChannelProbe {
|
|
32
|
+
channel: ChannelId;
|
|
33
|
+
runLocalChecks(context?: ChannelProbeContext): ReadinessCheckResult[];
|
|
34
|
+
runRemoteChecks?(context?: ChannelProbeContext): Promise<ReadinessCheckResult[]>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic retry sweep for failed channel inbound events.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parseChannelId, isChannelId } from '../channels/types.js';
|
|
6
|
+
import { getLogger } from '../util/logger.js';
|
|
7
|
+
import * as channelDeliveryStore from '../memory/channel-delivery-store.js';
|
|
8
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
9
|
+
import * as attachmentsStore from '../memory/attachments-store.js';
|
|
10
|
+
import { renderHistoryContent } from '../daemon/handlers.js';
|
|
11
|
+
import { deliverChannelReply } from './gateway-client.js';
|
|
12
|
+
import type { GuardianRuntimeContext } from '../daemon/session-runtime-assembly.js';
|
|
13
|
+
import type { MessageProcessor } from './http-types.js';
|
|
14
|
+
|
|
15
|
+
const log = getLogger('runtime-http');
|
|
16
|
+
|
|
17
|
+
function parseGuardianRuntimeContext(value: unknown): GuardianRuntimeContext | undefined {
|
|
18
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
19
|
+
const raw = value as Record<string, unknown>;
|
|
20
|
+
const actorRole = raw.actorRole;
|
|
21
|
+
if (
|
|
22
|
+
actorRole !== 'guardian'
|
|
23
|
+
&& actorRole !== 'non-guardian'
|
|
24
|
+
&& actorRole !== 'unverified_channel'
|
|
25
|
+
) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const rawSourceChannel = typeof raw.sourceChannel === 'string' && raw.sourceChannel.trim().length > 0
|
|
29
|
+
? raw.sourceChannel
|
|
30
|
+
: undefined;
|
|
31
|
+
if (!rawSourceChannel || !isChannelId(rawSourceChannel)) return undefined;
|
|
32
|
+
const sourceChannel = rawSourceChannel;
|
|
33
|
+
const denialReason =
|
|
34
|
+
raw.denialReason === 'no_binding' || raw.denialReason === 'no_identity'
|
|
35
|
+
? raw.denialReason
|
|
36
|
+
: undefined;
|
|
37
|
+
return {
|
|
38
|
+
sourceChannel,
|
|
39
|
+
actorRole,
|
|
40
|
+
guardianChatId: typeof raw.guardianChatId === 'string' ? raw.guardianChatId : undefined,
|
|
41
|
+
guardianExternalUserId: typeof raw.guardianExternalUserId === 'string' ? raw.guardianExternalUserId : undefined,
|
|
42
|
+
requesterIdentifier: typeof raw.requesterIdentifier === 'string' ? raw.requesterIdentifier : undefined,
|
|
43
|
+
requesterExternalUserId: typeof raw.requesterExternalUserId === 'string' ? raw.requesterExternalUserId : undefined,
|
|
44
|
+
requesterChatId: typeof raw.requesterChatId === 'string' ? raw.requesterChatId : undefined,
|
|
45
|
+
denialReason,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Periodically retry failed channel inbound events that have passed
|
|
51
|
+
* their exponential backoff delay.
|
|
52
|
+
*/
|
|
53
|
+
export async function sweepFailedEvents(
|
|
54
|
+
processMessage: MessageProcessor,
|
|
55
|
+
bearerToken: string | undefined,
|
|
56
|
+
): Promise<void> {
|
|
57
|
+
const events = channelDeliveryStore.getRetryableEvents();
|
|
58
|
+
if (events.length === 0) return;
|
|
59
|
+
|
|
60
|
+
log.info({ count: events.length }, 'Retrying failed channel inbound events');
|
|
61
|
+
|
|
62
|
+
for (const event of events) {
|
|
63
|
+
if (!event.rawPayload) {
|
|
64
|
+
// No payload stored -- can't replay, move to dead letter
|
|
65
|
+
channelDeliveryStore.recordProcessingFailure(
|
|
66
|
+
event.id,
|
|
67
|
+
new Error('No raw payload stored for replay'),
|
|
68
|
+
);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let payload: Record<string, unknown>;
|
|
73
|
+
try {
|
|
74
|
+
payload = JSON.parse(event.rawPayload) as Record<string, unknown>;
|
|
75
|
+
} catch {
|
|
76
|
+
channelDeliveryStore.recordProcessingFailure(
|
|
77
|
+
event.id,
|
|
78
|
+
new Error('Failed to parse stored raw payload'),
|
|
79
|
+
);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const content = typeof payload.content === 'string' ? payload.content.trim() : '';
|
|
84
|
+
const attachmentIds = Array.isArray(payload.attachmentIds) ? payload.attachmentIds as string[] : undefined;
|
|
85
|
+
const sourceChannel = parseChannelId(payload.sourceChannel);
|
|
86
|
+
if (!sourceChannel) {
|
|
87
|
+
channelDeliveryStore.recordProcessingFailure(
|
|
88
|
+
event.id,
|
|
89
|
+
new Error(`Invalid sourceChannel: ${String(payload.sourceChannel)}`),
|
|
90
|
+
);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const sourceMetadata = payload.sourceMetadata as Record<string, unknown> | undefined;
|
|
94
|
+
const assistantId = typeof payload.assistantId === 'string'
|
|
95
|
+
? payload.assistantId
|
|
96
|
+
: undefined;
|
|
97
|
+
const guardianContext = parseGuardianRuntimeContext(payload.guardianCtx);
|
|
98
|
+
|
|
99
|
+
const metadataHintsRaw = sourceMetadata?.hints;
|
|
100
|
+
const metadataHints = Array.isArray(metadataHintsRaw)
|
|
101
|
+
? metadataHintsRaw.filter((h): h is string => typeof h === 'string' && h.trim().length > 0)
|
|
102
|
+
: [];
|
|
103
|
+
const metadataUxBrief = typeof sourceMetadata?.uxBrief === 'string' && sourceMetadata.uxBrief.trim().length > 0
|
|
104
|
+
? sourceMetadata.uxBrief.trim()
|
|
105
|
+
: undefined;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const { messageId: userMessageId } = await processMessage(
|
|
109
|
+
event.conversationId,
|
|
110
|
+
content,
|
|
111
|
+
attachmentIds,
|
|
112
|
+
{
|
|
113
|
+
transport: {
|
|
114
|
+
channelId: sourceChannel,
|
|
115
|
+
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
116
|
+
uxBrief: metadataUxBrief,
|
|
117
|
+
},
|
|
118
|
+
assistantId,
|
|
119
|
+
guardianContext,
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
channelDeliveryStore.linkMessage(event.id, userMessageId);
|
|
123
|
+
channelDeliveryStore.markProcessed(event.id);
|
|
124
|
+
log.info({ eventId: event.id }, 'Successfully replayed failed channel event');
|
|
125
|
+
|
|
126
|
+
const replyCallbackUrl = typeof payload.replyCallbackUrl === 'string'
|
|
127
|
+
? payload.replyCallbackUrl
|
|
128
|
+
: undefined;
|
|
129
|
+
if (replyCallbackUrl) {
|
|
130
|
+
const externalChatId = typeof payload.externalChatId === 'string'
|
|
131
|
+
? payload.externalChatId
|
|
132
|
+
: undefined;
|
|
133
|
+
if (externalChatId) {
|
|
134
|
+
await deliverReplyViaCallback(
|
|
135
|
+
event.conversationId,
|
|
136
|
+
externalChatId,
|
|
137
|
+
replyCallbackUrl,
|
|
138
|
+
bearerToken,
|
|
139
|
+
assistantId,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
log.error({ err, eventId: event.id }, 'Retry failed for channel event');
|
|
145
|
+
channelDeliveryStore.recordProcessingFailure(event.id, err);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function deliverReplyViaCallback(
|
|
151
|
+
conversationId: string,
|
|
152
|
+
externalChatId: string,
|
|
153
|
+
callbackUrl: string,
|
|
154
|
+
bearerToken: string | undefined,
|
|
155
|
+
assistantId?: string,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const msgs = conversationStore.getMessages(conversationId);
|
|
158
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
159
|
+
if (msgs[i].role === 'assistant') {
|
|
160
|
+
let parsed: unknown;
|
|
161
|
+
try { parsed = JSON.parse(msgs[i].content); } catch { parsed = msgs[i].content; }
|
|
162
|
+
const rendered = renderHistoryContent(parsed);
|
|
163
|
+
|
|
164
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
|
|
165
|
+
const replyAttachments = linked.map((a) => ({
|
|
166
|
+
id: a.id,
|
|
167
|
+
filename: a.originalFilename,
|
|
168
|
+
mimeType: a.mimeType,
|
|
169
|
+
sizeBytes: a.sizeBytes,
|
|
170
|
+
kind: a.kind,
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
if (rendered.text || replyAttachments.length > 0) {
|
|
174
|
+
await deliverChannelReply(callbackUrl, {
|
|
175
|
+
chatId: externalChatId,
|
|
176
|
+
text: rendered.text || undefined,
|
|
177
|
+
attachments: replyAttachments.length > 0 ? replyAttachments : undefined,
|
|
178
|
+
assistantId,
|
|
179
|
+
}, bearerToken);
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|