@ouro.bot/cli 0.1.0-alpha.6 → 0.1.0-alpha.600
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 +229 -183
- package/RepairGuide.ouro/agent.json +5 -0
- package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
- package/RepairGuide.ouro/psyche/SOUL.md +55 -0
- package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
- package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
- package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
- package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
- package/SerpentGuide.ouro/agent.json +83 -0
- package/SerpentGuide.ouro/psyche/SOUL.md +25 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/assets/ouroboros.png +0 -0
- package/changelog.json +4182 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +117 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +254 -0
- package/dist/arc/packets.js +193 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +65 -0
- package/dist/heart/active-work.js +989 -0
- package/dist/heart/agent-entry.js +69 -3
- package/dist/heart/attachments/image-normalize.js +194 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/adapter.js +2 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +16 -0
- package/dist/heart/attachments/store.js +103 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +479 -0
- package/dist/heart/awaiting/await-alert.js +146 -0
- package/dist/heart/awaiting/await-expiry.js +108 -0
- package/dist/heart/awaiting/await-loader.js +91 -0
- package/dist/heart/awaiting/await-parser.js +141 -0
- package/dist/heart/awaiting/await-runtime-state.js +97 -0
- package/dist/heart/awaiting/await-scheduler.js +377 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +142 -0
- package/dist/heart/config-registry.js +322 -0
- package/dist/heart/config.js +164 -135
- package/dist/heart/core.js +1069 -260
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +419 -0
- package/dist/heart/daemon/agent-discovery.js +180 -0
- package/dist/heart/daemon/agent-service.js +522 -0
- package/dist/heart/daemon/agentic-repair.js +547 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +776 -0
- package/dist/heart/daemon/cli-exec.js +7571 -0
- package/dist/heart/daemon/cli-help.js +498 -0
- package/dist/heart/daemon/cli-parse.js +1599 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +763 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/daemon-cli.js +30 -758
- package/dist/heart/daemon/daemon-entry.js +540 -8
- package/dist/heart/daemon/daemon-health.js +176 -0
- package/dist/heart/daemon/daemon-rollup.js +57 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +287 -0
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +972 -20
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +873 -0
- package/dist/heart/daemon/health-monitor.js +122 -1
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +206 -0
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/human-command-screens.js +234 -0
- package/dist/heart/daemon/human-readiness.js +114 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/interactive-repair.js +394 -0
- package/dist/heart/daemon/launchd.js +188 -0
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/mcp-canary.js +297 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +14 -12
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/process-manager.js +381 -26
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/pulse.js +475 -0
- package/dist/heart/daemon/readiness-repair.js +365 -0
- package/dist/heart/daemon/run-hooks.js +39 -0
- package/dist/heart/daemon/runtime-logging.js +67 -16
- package/dist/heart/daemon/runtime-metadata.js +191 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +731 -0
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +349 -0
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +330 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/terminal-ui.js +499 -0
- package/dist/heart/daemon/thoughts.js +524 -0
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +189 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-runtime-state.js +100 -0
- package/dist/heart/habits/habit-scheduler.js +372 -0
- package/dist/heart/{daemon → hatch}/hatch-animation.js +10 -3
- package/dist/heart/{daemon → hatch}/hatch-flow.js +34 -136
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/hatch/specialist-orchestrator.js +129 -0
- package/dist/heart/hatch/specialist-prompt.js +102 -0
- package/dist/heart/hatch/specialist-tools.js +306 -0
- package/dist/heart/identity.js +281 -67
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +353 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +31 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +195 -0
- package/dist/heart/mailbox/readers/agent-machine.js +382 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +338 -0
- package/dist/heart/mailbox/readers/mail.js +367 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +651 -0
- package/dist/heart/mailbox/readers/sessions.js +232 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +656 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +59 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-attempt.js +134 -0
- package/dist/heart/provider-binding-resolver.js +267 -0
- package/dist/heart/provider-credentials.js +425 -0
- package/dist/heart/provider-failover.js +301 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +262 -0
- package/dist/heart/provider-readiness-cache.js +40 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +202 -50
- package/dist/heart/providers/azure.js +104 -13
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +29 -7
- package/dist/heart/providers/openai-codex.js +63 -39
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +367 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +70 -0
- package/dist/heart/session-activity.js +190 -0
- package/dist/heart/session-events.js +1149 -0
- package/dist/heart/session-playback-cli-main.js +5 -0
- package/dist/heart/session-playback-cli.js +36 -0
- package/dist/heart/session-playback.js +231 -0
- package/dist/heart/session-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +182 -0
- package/dist/heart/session-transcript.js +243 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +129 -34
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/timeouts.js +101 -0
- package/dist/heart/tool-activity-callbacks.js +59 -0
- package/dist/heart/tool-description.js +143 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +421 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/versioning/ouro-bot-global-installer.js +129 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +426 -0
- package/dist/heart/{daemon → versioning}/ouro-uti.js +11 -2
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/versioning/staged-restart.js +146 -0
- package/dist/heart/versioning/update-checker.js +116 -0
- package/dist/heart/versioning/update-hooks.js +142 -0
- package/dist/heart/versioning/wrapper-publish-guard.js +86 -0
- package/dist/mailbox-ui/assets/index-B-461hes.js +61 -0
- package/dist/mailbox-ui/assets/index-BPr5vNuM.css +1 -0
- package/dist/mailbox-ui/index.html +15 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +700 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +788 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +457 -0
- package/dist/mailroom/mbox-import.js +393 -0
- package/dist/mailroom/migration.js +164 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +233 -0
- package/dist/mailroom/search-cache.js +268 -0
- package/dist/mailroom/search-relevance.js +319 -0
- package/dist/mailroom/smtp-ingress.js +176 -0
- package/dist/mailroom/source-state.js +176 -0
- package/dist/mailroom/thread.js +109 -0
- package/dist/mailroom/travel-extract.js +89 -0
- package/dist/mind/bundle-manifest.js +77 -1
- package/dist/mind/context.js +174 -94
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +84 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +74 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/resolver.js +54 -2
- package/dist/mind/friends/store-file.js +58 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +10 -2
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/note-search.js +268 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1267 -130
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +22 -3
- package/dist/nerves/coverage/audit-rules.js +15 -6
- package/dist/nerves/coverage/audit.js +28 -2
- package/dist/nerves/coverage/cli.js +1 -1
- package/dist/nerves/coverage/contract.js +5 -5
- package/dist/nerves/coverage/file-completeness.js +129 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +224 -4
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/review/cli-main.js +5 -0
- package/dist/nerves/review/cli.js +156 -0
- package/dist/nerves/review/core.js +152 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +17 -56
- package/dist/repertoire/ado-semantic.js +11 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +997 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +180 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +220 -13
- package/dist/repertoire/coding/spawner.js +58 -12
- package/dist/repertoire/coding/tools.js +209 -7
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +178 -0
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +396 -0
- package/dist/repertoire/mcp-client.js +295 -0
- package/dist/repertoire/mcp-manager.js +362 -0
- package/dist/repertoire/mcp-tools.js +63 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +15 -24
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +39 -13
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +16 -79
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-awaiting.js +360 -0
- package/dist/repertoire/tools-base.js +56 -707
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-bridge.js +142 -0
- package/dist/repertoire/tools-bundle.js +984 -0
- package/dist/repertoire/tools-config.js +185 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-credential.js +381 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-mail.js +1916 -0
- package/dist/repertoire/tools-notes.js +421 -0
- package/dist/repertoire/tools-obligations.js +142 -0
- package/dist/repertoire/tools-runtime.js +61 -0
- package/dist/repertoire/tools-session.js +809 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-surface.js +345 -0
- package/dist/repertoire/tools-teams.js +64 -61
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +604 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools-voice.js +144 -0
- package/dist/repertoire/tools.js +154 -98
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +594 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +116 -0
- package/dist/senses/await-turn-message.js +58 -0
- package/dist/senses/bluebubbles/active-turns.js +216 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/bluebubbles/client.js +685 -0
- package/dist/senses/bluebubbles/entry.js +77 -0
- package/dist/senses/bluebubbles/inbound-log.js +126 -0
- package/dist/senses/bluebubbles/index.js +2548 -0
- package/dist/senses/bluebubbles/media.js +389 -0
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +45 -16
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/replay.js +137 -0
- package/dist/senses/bluebubbles/runtime-state.js +137 -0
- package/dist/senses/bluebubbles/session-cleanup.js +72 -0
- package/dist/senses/bluebubbles-meta-guard.js +40 -0
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +607 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +85 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +240 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +777 -264
- package/dist/senses/commands.js +66 -3
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +209 -16
- package/dist/senses/inner-dialog.js +682 -91
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +379 -0
- package/dist/senses/pipeline.js +751 -0
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +392 -0
- package/dist/senses/surface-tool.js +70 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +925 -195
- package/dist/senses/trust-gate.js +207 -2
- package/dist/senses/voice/audio-playback.js +237 -0
- package/dist/senses/voice/audio-routing.js +119 -0
- package/dist/senses/voice/elevenlabs.js +202 -0
- package/dist/senses/voice/floor-control.js +431 -0
- package/dist/senses/voice/floor-controller.js +115 -0
- package/dist/senses/voice/golden-path.js +116 -0
- package/dist/senses/voice/index.js +29 -0
- package/dist/senses/voice/meeting.js +113 -0
- package/dist/senses/voice/outbound.js +190 -0
- package/dist/senses/voice/phone.js +33 -0
- package/dist/senses/voice/playback.js +139 -0
- package/dist/senses/voice/realtime-eval.js +496 -0
- package/dist/senses/voice/realtime-trace.js +531 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +191 -0
- package/dist/senses/voice/twilio-phone-runtime.js +807 -0
- package/dist/senses/voice/twilio-phone.js +5077 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +161 -0
- package/dist/senses/voice-entry.js +81 -0
- package/dist/senses/voice-realtime-eval-command.js +99 -0
- package/dist/senses/voice-realtime-eval-entry.js +21 -0
- package/dist/senses/voice-twilio-entry.js +87 -0
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +265 -0
- package/package.json +52 -7
- package/skills/agent-commerce.md +106 -0
- package/skills/browser-navigation.md +117 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +99 -0
- package/skills/travel-planning.md +138 -0
- package/AdoptionSpecialist.ouro/agent.json +0 -20
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +0 -22
- package/dist/heart/daemon/specialist-orchestrator.js +0 -160
- package/dist/heart/daemon/specialist-prompt.js +0 -40
- package/dist/heart/daemon/specialist-session.js +0 -142
- package/dist/heart/daemon/specialist-tools.js +0 -128
- package/dist/heart/daemon/subagent-installer.js +0 -125
- package/dist/inner-worker-entry.js +0 -4
- package/dist/mind/associative-recall.js +0 -197
- package/dist/senses/bluebubbles-client.js +0 -279
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -332
- package/subagents/README.md +0 -73
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.agentScopedTwilioPhoneBasePath = agentScopedTwilioPhoneBasePath;
|
|
37
|
+
exports.resolveTwilioPhoneTransportRuntime = resolveTwilioPhoneTransportRuntime;
|
|
38
|
+
exports.startConfiguredTwilioPhoneTransport = startConfiguredTwilioPhoneTransport;
|
|
39
|
+
exports.placeConfiguredTwilioPhoneCall = placeConfiguredTwilioPhoneCall;
|
|
40
|
+
exports.closeTwilioPhoneBridgeServer = closeTwilioPhoneBridgeServer;
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const crypto = __importStar(require("node:crypto"));
|
|
43
|
+
const identity_1 = require("../../heart/identity");
|
|
44
|
+
const machine_identity_1 = require("../../heart/machine-identity");
|
|
45
|
+
const provider_credentials_1 = require("../../heart/provider-credentials");
|
|
46
|
+
const runtime_credentials_1 = require("../../heart/runtime-credentials");
|
|
47
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
48
|
+
const elevenlabs_1 = require("./elevenlabs");
|
|
49
|
+
const playback_1 = require("./playback");
|
|
50
|
+
const transcript_1 = require("./transcript");
|
|
51
|
+
const twilio_phone_1 = require("./twilio-phone");
|
|
52
|
+
const turn_1 = require("./turn");
|
|
53
|
+
const whisper_1 = require("./whisper");
|
|
54
|
+
function asRecord(value) {
|
|
55
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
56
|
+
}
|
|
57
|
+
function configString(config, dottedPath) {
|
|
58
|
+
let cursor = config;
|
|
59
|
+
for (const segment of dottedPath.split(".")) {
|
|
60
|
+
const record = asRecord(cursor);
|
|
61
|
+
if (!record)
|
|
62
|
+
return undefined;
|
|
63
|
+
cursor = record[segment];
|
|
64
|
+
}
|
|
65
|
+
return typeof cursor === "string" && cursor.trim() ? cursor.trim() : undefined;
|
|
66
|
+
}
|
|
67
|
+
function configNumber(config, dottedPath) {
|
|
68
|
+
let cursor = config;
|
|
69
|
+
for (const segment of dottedPath.split(".")) {
|
|
70
|
+
const record = asRecord(cursor);
|
|
71
|
+
if (!record)
|
|
72
|
+
return undefined;
|
|
73
|
+
cursor = record[segment];
|
|
74
|
+
}
|
|
75
|
+
if (typeof cursor === "number" && Number.isFinite(cursor))
|
|
76
|
+
return cursor;
|
|
77
|
+
if (typeof cursor === "string" && cursor.trim()) {
|
|
78
|
+
const parsed = Number(cursor);
|
|
79
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
function configBoolean(config, dottedPath) {
|
|
84
|
+
let cursor = config;
|
|
85
|
+
for (const segment of dottedPath.split(".")) {
|
|
86
|
+
const record = asRecord(cursor);
|
|
87
|
+
if (!record)
|
|
88
|
+
return undefined;
|
|
89
|
+
cursor = record[segment];
|
|
90
|
+
}
|
|
91
|
+
if (typeof cursor === "boolean")
|
|
92
|
+
return cursor;
|
|
93
|
+
if (typeof cursor === "string") {
|
|
94
|
+
const normalized = cursor.trim().toLowerCase();
|
|
95
|
+
if (normalized === "true")
|
|
96
|
+
return true;
|
|
97
|
+
if (normalized === "false")
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
function requireConfig(result, label) {
|
|
103
|
+
if (result.ok)
|
|
104
|
+
return result.config;
|
|
105
|
+
throw new Error(`${label} unavailable: ${result.error}`);
|
|
106
|
+
}
|
|
107
|
+
function required(value, guidance) {
|
|
108
|
+
if (value)
|
|
109
|
+
return value;
|
|
110
|
+
throw new Error(guidance);
|
|
111
|
+
}
|
|
112
|
+
function selectedAgentProviders(config) {
|
|
113
|
+
const providers = new Set();
|
|
114
|
+
providers.add(config.humanFacing.provider);
|
|
115
|
+
providers.add(config.agentFacing.provider);
|
|
116
|
+
if (config.provider)
|
|
117
|
+
providers.add(config.provider);
|
|
118
|
+
return [...providers];
|
|
119
|
+
}
|
|
120
|
+
async function cacheSelectedProviderCredentials(agentName) {
|
|
121
|
+
const providers = selectedAgentProviders((0, identity_1.loadAgentConfig)());
|
|
122
|
+
const cached = (0, provider_credentials_1.readProviderCredentialPool)(agentName);
|
|
123
|
+
/* v8 ignore next -- fast path is covered by provider credential pool tests; voice runtime tests exercise refresh/repair paths @preserve */
|
|
124
|
+
if (cached.ok && providers.every((provider) => cached.pool.providers[provider]))
|
|
125
|
+
return;
|
|
126
|
+
const pool = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, { providers });
|
|
127
|
+
if (!pool.ok) {
|
|
128
|
+
throw new Error(`provider credentials unavailable for phone voice: ${pool.error}`);
|
|
129
|
+
}
|
|
130
|
+
const missing = providers.filter((provider) => !pool.pool.providers[provider]);
|
|
131
|
+
if (missing.length > 0) {
|
|
132
|
+
throw new Error(`missing provider credentials for phone voice: ${missing.join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function agentPathSegment(agentName) {
|
|
136
|
+
return agentName.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
137
|
+
}
|
|
138
|
+
function trimOptional(value) {
|
|
139
|
+
return value?.trim() || undefined;
|
|
140
|
+
}
|
|
141
|
+
function resolveOpenAIRealtimeApiKey(options) {
|
|
142
|
+
const overrideKey = trimOptional(options.overrides?.openaiRealtimeApiKey);
|
|
143
|
+
/* v8 ignore next -- operator CLI overrides use the same validated setting shape as stored runtime config @preserve */
|
|
144
|
+
if (overrideKey)
|
|
145
|
+
return { apiKey: overrideKey, source: "override.openaiRealtimeApiKey" };
|
|
146
|
+
const voiceKey = configString(options.runtimeConfig, "voice.openaiRealtimeApiKey");
|
|
147
|
+
if (voiceKey)
|
|
148
|
+
return { apiKey: voiceKey, source: "voice.openaiRealtimeApiKey" };
|
|
149
|
+
const integrationKey = configString(options.runtimeConfig, "integrations.openaiApiKey");
|
|
150
|
+
if (integrationKey)
|
|
151
|
+
return { apiKey: integrationKey, source: "integrations.openaiApiKey" };
|
|
152
|
+
const compatKey = configString(options.runtimeConfig, "integrations.openaiEmbeddingsApiKey");
|
|
153
|
+
if (compatKey)
|
|
154
|
+
return { apiKey: compatKey, source: "integrations.openaiEmbeddingsApiKey" };
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
function configuredConversationEngine(options, overrides, transportMode) {
|
|
158
|
+
const explicit = overrides.conversationEngine
|
|
159
|
+
?? configString(options.machineConfig, "voice.twilioConversationEngine")
|
|
160
|
+
?? configString(options.machineConfig, "voice.conversationEngine")
|
|
161
|
+
?? configString(options.runtimeConfig, "voice.twilioConversationEngine")
|
|
162
|
+
?? configString(options.runtimeConfig, "voice.conversationEngine");
|
|
163
|
+
const hasSipConfig = !!(configString(options.runtimeConfig, "voice.openaiSipProjectId")
|
|
164
|
+
|| configString(options.machineConfig, "voice.openaiSipProjectId"));
|
|
165
|
+
const explicitEngine = explicit ? (0, twilio_phone_1.normalizeTwilioPhoneConversationEngine)(explicit) : undefined;
|
|
166
|
+
if (hasSipConfig && (!explicitEngine || explicitEngine === "cascade"))
|
|
167
|
+
return "openai-sip";
|
|
168
|
+
if (explicitEngine)
|
|
169
|
+
return explicitEngine;
|
|
170
|
+
const hasRealtimeConfig = !!resolveOpenAIRealtimeApiKey({ runtimeConfig: options.runtimeConfig, overrides });
|
|
171
|
+
if (hasRealtimeConfig && transportMode === "media-stream")
|
|
172
|
+
return "openai-realtime";
|
|
173
|
+
return "cascade";
|
|
174
|
+
}
|
|
175
|
+
function configuredOutboundConversationEngine(options, overrides, conversationEngine, transportMode) {
|
|
176
|
+
const defaultOutboundEngine = conversationEngine === "openai-sip" && transportMode === "media-stream"
|
|
177
|
+
? "openai-realtime"
|
|
178
|
+
: conversationEngine;
|
|
179
|
+
const configured = overrides.outboundConversationEngine
|
|
180
|
+
?? (0, twilio_phone_1.normalizeTwilioPhoneConversationEngine)(configString(options.machineConfig, "voice.twilioOutboundConversationEngine")
|
|
181
|
+
?? configString(options.machineConfig, "voice.outboundConversationEngine")
|
|
182
|
+
?? configString(options.runtimeConfig, "voice.twilioOutboundConversationEngine")
|
|
183
|
+
?? configString(options.runtimeConfig, "voice.outboundConversationEngine")
|
|
184
|
+
?? defaultOutboundEngine);
|
|
185
|
+
if (defaultOutboundEngine === "openai-realtime" && configured === "cascade")
|
|
186
|
+
return defaultOutboundEngine;
|
|
187
|
+
return configured;
|
|
188
|
+
}
|
|
189
|
+
function normalizeOpenAIRealtimeReasoningEffort(value) {
|
|
190
|
+
const normalized = value?.trim().toLowerCase();
|
|
191
|
+
if (normalized === "minimal"
|
|
192
|
+
|| normalized === "low"
|
|
193
|
+
|| normalized === "medium"
|
|
194
|
+
|| normalized === "high"
|
|
195
|
+
|| normalized === "xhigh") {
|
|
196
|
+
return normalized;
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
function normalizeOpenAIRealtimeNoiseReduction(value) {
|
|
201
|
+
const normalized = value?.trim().toLowerCase();
|
|
202
|
+
if (normalized === "near_field" || normalized === "far_field" || normalized === "none")
|
|
203
|
+
return normalized;
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
function normalizeOpenAIRealtimeTurnDetectionMode(value) {
|
|
207
|
+
const normalized = value?.trim().toLowerCase();
|
|
208
|
+
/* v8 ignore next -- mode values are passed through from OpenAI config; invalid fallback is the important fail-soft path @preserve */
|
|
209
|
+
if (normalized === "server_vad" || normalized === "semantic_vad")
|
|
210
|
+
return normalized;
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
function normalizeOpenAIRealtimeVadEagerness(value) {
|
|
214
|
+
const normalized = value?.trim().toLowerCase();
|
|
215
|
+
/* v8 ignore next -- eagerness values are passed through from OpenAI config; invalid fallback is the important fail-soft path @preserve */
|
|
216
|
+
if (normalized === "low" || normalized === "medium" || normalized === "high" || normalized === "auto")
|
|
217
|
+
return normalized;
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
function errorMessage(error) {
|
|
221
|
+
/* v8 ignore next -- thrown runtime values are Errors in supported call paths @preserve */
|
|
222
|
+
return error instanceof Error ? error.message : String(error);
|
|
223
|
+
}
|
|
224
|
+
function agentScopedTwilioPhoneBasePath(agentName) {
|
|
225
|
+
return `/voice/agents/${agentPathSegment(agentName)}/twilio`;
|
|
226
|
+
}
|
|
227
|
+
function resolveTwilioPhoneTransportRuntime(options) {
|
|
228
|
+
const overrides = options.overrides ?? {};
|
|
229
|
+
const configuredPublicBaseUrl = trimOptional(overrides.publicBaseUrl)
|
|
230
|
+
?? configString(options.machineConfig, "voice.twilioPublicUrl");
|
|
231
|
+
const explicitEnabled = overrides.enabled ?? configBoolean(options.machineConfig, "voice.twilioEnabled");
|
|
232
|
+
if (!configuredPublicBaseUrl && options.requirePublicUrl) {
|
|
233
|
+
throw new Error("missing voice.twilioPublicUrl in this machine's runtime config");
|
|
234
|
+
}
|
|
235
|
+
const enabled = explicitEnabled ?? !!configuredPublicBaseUrl;
|
|
236
|
+
if (!enabled) {
|
|
237
|
+
return { status: "disabled", reason: "voice.twilioPublicUrl is not configured" };
|
|
238
|
+
}
|
|
239
|
+
if (!configuredPublicBaseUrl) {
|
|
240
|
+
throw new Error("missing voice.twilioPublicUrl in this machine's runtime config");
|
|
241
|
+
}
|
|
242
|
+
const publicUrl = new URL(configuredPublicBaseUrl);
|
|
243
|
+
if (publicUrl.protocol !== "https:") {
|
|
244
|
+
throw new Error("voice.twilioPublicUrl must be an https URL");
|
|
245
|
+
}
|
|
246
|
+
const publicBaseUrl = publicUrl.toString();
|
|
247
|
+
const basePath = (0, twilio_phone_1.normalizeTwilioPhoneBasePath)(overrides.basePath
|
|
248
|
+
?? configString(options.machineConfig, "voice.twilioBasePath")
|
|
249
|
+
?? options.defaultBasePath
|
|
250
|
+
?? twilio_phone_1.TWILIO_PHONE_WEBHOOK_BASE_PATH);
|
|
251
|
+
const explicitTransportModeString = configString(options.machineConfig, "voice.twilioTransportMode");
|
|
252
|
+
// When the operator has only configured OpenAI Realtime (key) or OpenAI SIP
|
|
253
|
+
// (project id) and not picked a transport mode, infer media-stream — the
|
|
254
|
+
// legacy `record-play` default would otherwise pin `conversationEngine` to
|
|
255
|
+
// `cascade`, route inbound calls through the ElevenLabs/Whisper greeting
|
|
256
|
+
// path the operator never configured, and produce a fully silent first
|
|
257
|
+
// turn ("no greeting at all"). Realtime requires media-stream by nature.
|
|
258
|
+
const hasRealtimeApiKey = !!resolveOpenAIRealtimeApiKey({ runtimeConfig: options.runtimeConfig, overrides });
|
|
259
|
+
const hasSipProjectConfig = !!(configString(options.runtimeConfig, "voice.openaiSipProjectId")
|
|
260
|
+
|| configString(options.machineConfig, "voice.openaiSipProjectId"));
|
|
261
|
+
const realtimeImpliesMediaStream = hasRealtimeApiKey || hasSipProjectConfig;
|
|
262
|
+
const transportMode = overrides.transportMode
|
|
263
|
+
?? (0, twilio_phone_1.normalizeTwilioPhoneTransportMode)(explicitTransportModeString
|
|
264
|
+
?? (realtimeImpliesMediaStream ? "media-stream" : twilio_phone_1.DEFAULT_TWILIO_PHONE_TRANSPORT_MODE));
|
|
265
|
+
const conversationEngine = configuredConversationEngine(options, overrides, transportMode);
|
|
266
|
+
const outboundConversationEngine = configuredOutboundConversationEngine(options, overrides, conversationEngine, transportMode);
|
|
267
|
+
const needsOpenAIRealtime = conversationEngine === "openai-realtime"
|
|
268
|
+
|| conversationEngine === "openai-sip"
|
|
269
|
+
|| outboundConversationEngine === "openai-realtime"
|
|
270
|
+
|| outboundConversationEngine === "openai-sip";
|
|
271
|
+
const needsOpenAISip = conversationEngine === "openai-sip" || outboundConversationEngine === "openai-sip";
|
|
272
|
+
const needsCascade = conversationEngine === "cascade" || outboundConversationEngine === "cascade";
|
|
273
|
+
let elevenLabsApiKey = configString(options.runtimeConfig, "integrations.elevenLabsApiKey") ?? "";
|
|
274
|
+
let elevenLabsVoiceId = trimOptional(overrides.elevenLabsVoiceId)
|
|
275
|
+
?? configString(options.runtimeConfig, "integrations.elevenLabsVoiceId")
|
|
276
|
+
?? configString(options.runtimeConfig, "voice.elevenLabsVoiceId")
|
|
277
|
+
?? "";
|
|
278
|
+
let whisperCliPath = trimOptional(overrides.whisperCliPath)
|
|
279
|
+
?? configString(options.machineConfig, "voice.whisperCliPath")
|
|
280
|
+
?? "";
|
|
281
|
+
let whisperModelPath = trimOptional(overrides.whisperModelPath)
|
|
282
|
+
?? configString(options.machineConfig, "voice.whisperModelPath")
|
|
283
|
+
?? "";
|
|
284
|
+
let openaiRealtime;
|
|
285
|
+
let openaiSip;
|
|
286
|
+
if (needsOpenAIRealtime) {
|
|
287
|
+
if ((conversationEngine === "openai-realtime" || outboundConversationEngine === "openai-realtime") && transportMode !== "media-stream") {
|
|
288
|
+
throw new Error("voice.twilioConversationEngine/openai-realtime requires voice.twilioTransportMode=media-stream");
|
|
289
|
+
}
|
|
290
|
+
const key = resolveOpenAIRealtimeApiKey({ runtimeConfig: options.runtimeConfig, overrides });
|
|
291
|
+
if (!key) {
|
|
292
|
+
throw new Error("missing voice.openaiRealtimeApiKey; save an OpenAI Realtime-capable API key before starting phone voice");
|
|
293
|
+
}
|
|
294
|
+
const turnDetection = {
|
|
295
|
+
mode: overrides.openaiRealtimeTurnDetectionMode
|
|
296
|
+
?? normalizeOpenAIRealtimeTurnDetectionMode(configString(options.machineConfig, "voice.openaiRealtimeTurnDetectionMode"))
|
|
297
|
+
?? normalizeOpenAIRealtimeTurnDetectionMode(configString(options.runtimeConfig, "voice.openaiRealtimeTurnDetectionMode")),
|
|
298
|
+
threshold: overrides.openaiRealtimeVadThreshold
|
|
299
|
+
?? configNumber(options.machineConfig, "voice.openaiRealtimeVadThreshold")
|
|
300
|
+
?? configNumber(options.runtimeConfig, "voice.openaiRealtimeVadThreshold"),
|
|
301
|
+
prefixPaddingMs: overrides.openaiRealtimeVadPrefixPaddingMs
|
|
302
|
+
?? configNumber(options.machineConfig, "voice.openaiRealtimeVadPrefixPaddingMs")
|
|
303
|
+
?? configNumber(options.runtimeConfig, "voice.openaiRealtimeVadPrefixPaddingMs"),
|
|
304
|
+
silenceDurationMs: overrides.openaiRealtimeVadSilenceDurationMs
|
|
305
|
+
?? configNumber(options.machineConfig, "voice.openaiRealtimeVadSilenceDurationMs")
|
|
306
|
+
?? configNumber(options.runtimeConfig, "voice.openaiRealtimeVadSilenceDurationMs"),
|
|
307
|
+
idleTimeoutMs: overrides.openaiRealtimeVadIdleTimeoutMs
|
|
308
|
+
?? configNumber(options.machineConfig, "voice.openaiRealtimeVadIdleTimeoutMs")
|
|
309
|
+
?? configNumber(options.runtimeConfig, "voice.openaiRealtimeVadIdleTimeoutMs"),
|
|
310
|
+
eagerness: overrides.openaiRealtimeVadEagerness
|
|
311
|
+
?? normalizeOpenAIRealtimeVadEagerness(configString(options.machineConfig, "voice.openaiRealtimeVadEagerness"))
|
|
312
|
+
?? normalizeOpenAIRealtimeVadEagerness(configString(options.runtimeConfig, "voice.openaiRealtimeVadEagerness")),
|
|
313
|
+
createResponse: overrides.openaiRealtimeVadCreateResponse
|
|
314
|
+
?? configBoolean(options.machineConfig, "voice.openaiRealtimeVadCreateResponse")
|
|
315
|
+
?? configBoolean(options.runtimeConfig, "voice.openaiRealtimeVadCreateResponse"),
|
|
316
|
+
interruptResponse: overrides.openaiRealtimeVadInterruptResponse
|
|
317
|
+
?? configBoolean(options.machineConfig, "voice.openaiRealtimeVadInterruptResponse")
|
|
318
|
+
?? configBoolean(options.runtimeConfig, "voice.openaiRealtimeVadInterruptResponse"),
|
|
319
|
+
};
|
|
320
|
+
openaiRealtime = {
|
|
321
|
+
apiKey: key.apiKey,
|
|
322
|
+
apiKeySource: key.source,
|
|
323
|
+
model: trimOptional(overrides.openaiRealtimeModel)
|
|
324
|
+
?? configString(options.machineConfig, "voice.openaiRealtimeModel")
|
|
325
|
+
?? configString(options.runtimeConfig, "voice.openaiRealtimeModel"),
|
|
326
|
+
voice: trimOptional(overrides.openaiRealtimeVoice)
|
|
327
|
+
?? configString(options.runtimeConfig, "voice.openaiRealtimeVoice")
|
|
328
|
+
?? configString(options.machineConfig, "voice.openaiRealtimeVoice"),
|
|
329
|
+
voiceStyle: trimOptional(overrides.openaiRealtimeVoiceStyle)
|
|
330
|
+
?? configString(options.runtimeConfig, "voice.openaiRealtimeVoiceStyle")
|
|
331
|
+
?? configString(options.machineConfig, "voice.openaiRealtimeVoiceStyle"),
|
|
332
|
+
voiceSpeed: overrides.openaiRealtimeVoiceSpeed
|
|
333
|
+
?? configNumber(options.runtimeConfig, "voice.openaiRealtimeVoiceSpeed")
|
|
334
|
+
?? configNumber(options.machineConfig, "voice.openaiRealtimeVoiceSpeed"),
|
|
335
|
+
websocketUrl: trimOptional(overrides.openaiRealtimeWebsocketUrl)
|
|
336
|
+
?? configString(options.machineConfig, "voice.openaiRealtimeWebsocketUrl")
|
|
337
|
+
?? configString(options.runtimeConfig, "voice.openaiRealtimeWebsocketUrl"),
|
|
338
|
+
reasoningEffort: overrides.openaiRealtimeReasoningEffort
|
|
339
|
+
?? normalizeOpenAIRealtimeReasoningEffort(configString(options.machineConfig, "voice.openaiRealtimeReasoningEffort"))
|
|
340
|
+
?? normalizeOpenAIRealtimeReasoningEffort(configString(options.runtimeConfig, "voice.openaiRealtimeReasoningEffort")),
|
|
341
|
+
noiseReduction: overrides.openaiRealtimeNoiseReduction
|
|
342
|
+
?? normalizeOpenAIRealtimeNoiseReduction(configString(options.machineConfig, "voice.openaiRealtimeNoiseReduction"))
|
|
343
|
+
?? normalizeOpenAIRealtimeNoiseReduction(configString(options.runtimeConfig, "voice.openaiRealtimeNoiseReduction")),
|
|
344
|
+
turnDetection,
|
|
345
|
+
};
|
|
346
|
+
if (needsOpenAISip) {
|
|
347
|
+
const projectId = trimOptional(overrides.openaiSipProjectId)
|
|
348
|
+
?? configString(options.runtimeConfig, "voice.openaiSipProjectId")
|
|
349
|
+
?? configString(options.machineConfig, "voice.openaiSipProjectId");
|
|
350
|
+
if (!projectId) {
|
|
351
|
+
throw new Error("missing voice.openaiSipProjectId; save the OpenAI project id before starting SIP phone voice");
|
|
352
|
+
}
|
|
353
|
+
const allowUnsignedWebhooks = overrides.openaiSipAllowUnsignedWebhooks
|
|
354
|
+
?? configBoolean(options.machineConfig, "voice.openaiSipAllowUnsignedWebhooks")
|
|
355
|
+
?? configBoolean(options.runtimeConfig, "voice.openaiSipAllowUnsignedWebhooks");
|
|
356
|
+
const webhookSecret = trimOptional(overrides.openaiSipWebhookSecret)
|
|
357
|
+
?? configString(options.runtimeConfig, "voice.openaiSipWebhookSecret")
|
|
358
|
+
?? configString(options.machineConfig, "voice.openaiSipWebhookSecret");
|
|
359
|
+
if (!webhookSecret && !allowUnsignedWebhooks) {
|
|
360
|
+
throw new Error("missing voice.openaiSipWebhookSecret; save the OpenAI webhook signing secret before starting SIP phone voice");
|
|
361
|
+
}
|
|
362
|
+
openaiSip = {
|
|
363
|
+
projectId,
|
|
364
|
+
webhookPath: (0, twilio_phone_1.normalizeTwilioPhoneBasePath)(trimOptional(overrides.openaiSipWebhookPath)
|
|
365
|
+
?? configString(options.machineConfig, "voice.openaiSipWebhookPath")
|
|
366
|
+
?? configString(options.runtimeConfig, "voice.openaiSipWebhookPath")
|
|
367
|
+
?? (0, twilio_phone_1.openAISipWebhookPath)(options.agentName)),
|
|
368
|
+
/* v8 ignore next 2 -- unsigned-webhook local-dev mode is resolved by config contract tests; production uses a secret @preserve */
|
|
369
|
+
...(webhookSecret ? { webhookSecret } : {}),
|
|
370
|
+
...(allowUnsignedWebhooks !== undefined ? { allowUnsignedWebhooks } : {}),
|
|
371
|
+
apiBaseUrl: trimOptional(overrides.openaiSipApiBaseUrl)
|
|
372
|
+
?? configString(options.machineConfig, "voice.openaiSipApiBaseUrl")
|
|
373
|
+
?? configString(options.runtimeConfig, "voice.openaiSipApiBaseUrl"),
|
|
374
|
+
websocketBaseUrl: trimOptional(overrides.openaiSipWebsocketBaseUrl)
|
|
375
|
+
?? configString(options.machineConfig, "voice.openaiSipWebsocketBaseUrl")
|
|
376
|
+
?? configString(options.runtimeConfig, "voice.openaiSipWebsocketBaseUrl"),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (needsCascade) {
|
|
381
|
+
elevenLabsApiKey = required(elevenLabsApiKey || undefined, "missing integrations.elevenLabsApiKey; run 'ouro connect voice --agent <agent>' for setup guidance");
|
|
382
|
+
elevenLabsVoiceId = required(elevenLabsVoiceId || undefined, "missing integrations.elevenLabsVoiceId; save the ElevenLabs voice ID before starting phone voice");
|
|
383
|
+
whisperCliPath = required(whisperCliPath || undefined, "missing voice.whisperCliPath in this machine's runtime config");
|
|
384
|
+
whisperModelPath = required(whisperModelPath || undefined, "missing voice.whisperModelPath in this machine's runtime config");
|
|
385
|
+
}
|
|
386
|
+
const outputDir = trimOptional(overrides.outputDir)
|
|
387
|
+
?? configString(options.machineConfig, "voice.twilioOutputDir")
|
|
388
|
+
?? path.join((0, identity_1.getAgentRoot)(options.agentName), "state", "voice", "twilio-phone");
|
|
389
|
+
const settings = {
|
|
390
|
+
agentName: options.agentName,
|
|
391
|
+
publicBaseUrl,
|
|
392
|
+
basePath,
|
|
393
|
+
webhookUrl: (0, twilio_phone_1.twilioPhoneWebhookUrl)(publicBaseUrl, basePath),
|
|
394
|
+
outputDir,
|
|
395
|
+
port: overrides.port
|
|
396
|
+
?? configNumber(options.machineConfig, "voice.twilioPort")
|
|
397
|
+
?? twilio_phone_1.DEFAULT_TWILIO_PHONE_PORT,
|
|
398
|
+
host: trimOptional(overrides.host)
|
|
399
|
+
?? configString(options.machineConfig, "voice.twilioHost")
|
|
400
|
+
?? "127.0.0.1",
|
|
401
|
+
elevenLabsApiKey,
|
|
402
|
+
elevenLabsVoiceId,
|
|
403
|
+
whisperCliPath,
|
|
404
|
+
whisperModelPath,
|
|
405
|
+
twilioAccountSid: configString(options.runtimeConfig, "voice.twilioAccountSid"),
|
|
406
|
+
twilioAuthToken: configString(options.runtimeConfig, "voice.twilioAuthToken"),
|
|
407
|
+
twilioFromNumber: trimOptional(overrides.twilioFromNumber)
|
|
408
|
+
?? configString(options.runtimeConfig, "voice.twilioFromNumber")
|
|
409
|
+
?? configString(options.machineConfig, "voice.twilioFromNumber"),
|
|
410
|
+
defaultFriendId: trimOptional(overrides.defaultFriendId)
|
|
411
|
+
?? configString(options.machineConfig, "voice.twilioDefaultFriendId"),
|
|
412
|
+
recordTimeoutSeconds: overrides.recordTimeoutSeconds
|
|
413
|
+
?? configNumber(options.machineConfig, "voice.twilioRecordTimeoutSeconds")
|
|
414
|
+
?? twilio_phone_1.DEFAULT_TWILIO_RECORD_TIMEOUT_SECONDS,
|
|
415
|
+
recordMaxLengthSeconds: overrides.recordMaxLengthSeconds
|
|
416
|
+
?? configNumber(options.machineConfig, "voice.twilioRecordMaxLengthSeconds")
|
|
417
|
+
?? twilio_phone_1.DEFAULT_TWILIO_RECORD_MAX_LENGTH_SECONDS,
|
|
418
|
+
greetingPrebufferMs: overrides.greetingPrebufferMs
|
|
419
|
+
?? configNumber(options.machineConfig, "voice.twilioGreetingPrebufferMs")
|
|
420
|
+
?? twilio_phone_1.DEFAULT_TWILIO_GREETING_PREBUFFER_MS,
|
|
421
|
+
playbackMode: overrides.playbackMode
|
|
422
|
+
?? (0, twilio_phone_1.normalizeTwilioPhonePlaybackMode)(configString(options.machineConfig, "voice.twilioPlaybackMode") ?? twilio_phone_1.DEFAULT_TWILIO_PHONE_PLAYBACK_MODE),
|
|
423
|
+
transportMode,
|
|
424
|
+
conversationEngine,
|
|
425
|
+
outboundConversationEngine,
|
|
426
|
+
openaiRealtime,
|
|
427
|
+
openaiSip,
|
|
428
|
+
openaiSipWebhookUrl: openaiSip?.webhookPath ? (0, twilio_phone_1.openAISipWebhookUrl)(publicBaseUrl, openaiSip.webhookPath) : undefined,
|
|
429
|
+
};
|
|
430
|
+
return { status: "configured", settings };
|
|
431
|
+
}
|
|
432
|
+
const defaultTwilioPhoneTransportRuntimeDeps = {
|
|
433
|
+
waitForRuntimeCredentialBootstrap: runtime_credentials_1.waitForRuntimeCredentialBootstrap,
|
|
434
|
+
loadMachineIdentity: machine_identity_1.loadOrCreateMachineIdentity,
|
|
435
|
+
refreshRuntimeConfig: runtime_credentials_1.refreshRuntimeCredentialConfig,
|
|
436
|
+
refreshMachineRuntimeConfig: runtime_credentials_1.refreshMachineRuntimeCredentialConfig,
|
|
437
|
+
readRuntimeConfig: runtime_credentials_1.readRuntimeCredentialConfig,
|
|
438
|
+
readMachineRuntimeConfig: runtime_credentials_1.readMachineRuntimeCredentialConfig,
|
|
439
|
+
cacheSelectedProviderCredentials,
|
|
440
|
+
createTranscriber: whisper_1.createWhisperCppTranscriber,
|
|
441
|
+
createTts: elevenlabs_1.createElevenLabsTtsClient,
|
|
442
|
+
startBridgeServer: twilio_phone_1.startTwilioPhoneBridgeServer,
|
|
443
|
+
};
|
|
444
|
+
const defaultTwilioPhoneOutboundCallRuntimeDeps = {
|
|
445
|
+
waitForRuntimeCredentialBootstrap: runtime_credentials_1.waitForRuntimeCredentialBootstrap,
|
|
446
|
+
loadMachineIdentity: machine_identity_1.loadOrCreateMachineIdentity,
|
|
447
|
+
refreshRuntimeConfig: runtime_credentials_1.refreshRuntimeCredentialConfig,
|
|
448
|
+
refreshMachineRuntimeConfig: runtime_credentials_1.refreshMachineRuntimeCredentialConfig,
|
|
449
|
+
readRuntimeConfig: runtime_credentials_1.readRuntimeCredentialConfig,
|
|
450
|
+
readMachineRuntimeConfig: runtime_credentials_1.readMachineRuntimeCredentialConfig,
|
|
451
|
+
createTts: elevenlabs_1.createElevenLabsTtsClient,
|
|
452
|
+
runVoiceLoopbackTurn: turn_1.runVoiceLoopbackTurn,
|
|
453
|
+
writeVoicePlaybackArtifact: playback_1.writeVoicePlaybackArtifact,
|
|
454
|
+
createOutboundCall: twilio_phone_1.createTwilioOutboundCall,
|
|
455
|
+
};
|
|
456
|
+
async function readFreshRuntimeSettings(agentName, overrides, defaultBasePath, requirePublicUrl, deps, options = {}) {
|
|
457
|
+
if (options.preferCached) {
|
|
458
|
+
const cachedRuntimeConfig = deps.readRuntimeConfig(agentName);
|
|
459
|
+
const cachedMachineConfig = deps.readMachineRuntimeConfig(agentName);
|
|
460
|
+
/* v8 ignore next -- stale/missing cache falls through to the refresh path; tests cover enabled and disabled cached states @preserve */
|
|
461
|
+
if (cachedRuntimeConfig.ok && cachedMachineConfig.ok) {
|
|
462
|
+
const resolution = resolveTwilioPhoneTransportRuntime({
|
|
463
|
+
agentName,
|
|
464
|
+
runtimeConfig: cachedRuntimeConfig.config,
|
|
465
|
+
machineConfig: cachedMachineConfig.config,
|
|
466
|
+
overrides,
|
|
467
|
+
defaultBasePath,
|
|
468
|
+
requirePublicUrl,
|
|
469
|
+
});
|
|
470
|
+
if (resolution.status === "disabled") {
|
|
471
|
+
throw new Error(`Twilio phone voice transport is disabled: ${resolution.reason}`);
|
|
472
|
+
}
|
|
473
|
+
return resolution.settings;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const bootstrapped = await deps.waitForRuntimeCredentialBootstrap(agentName);
|
|
477
|
+
const hasBootstrappedConfig = bootstrapped
|
|
478
|
+
&& deps.readRuntimeConfig(agentName).ok
|
|
479
|
+
&& deps.readMachineRuntimeConfig(agentName).ok;
|
|
480
|
+
if (!hasBootstrappedConfig) {
|
|
481
|
+
const machine = deps.loadMachineIdentity();
|
|
482
|
+
await Promise.all([
|
|
483
|
+
deps.refreshRuntimeConfig(agentName, { preserveCachedOnFailure: true }).catch(() => undefined),
|
|
484
|
+
deps.refreshMachineRuntimeConfig(agentName, machine.machineId, { preserveCachedOnFailure: true }).catch(() => undefined),
|
|
485
|
+
]);
|
|
486
|
+
}
|
|
487
|
+
const runtimeConfig = requireConfig(deps.readRuntimeConfig(agentName), "portable runtime/config");
|
|
488
|
+
const machineConfig = requireConfig(deps.readMachineRuntimeConfig(agentName), "machine runtime config");
|
|
489
|
+
const resolution = resolveTwilioPhoneTransportRuntime({
|
|
490
|
+
agentName,
|
|
491
|
+
runtimeConfig,
|
|
492
|
+
machineConfig,
|
|
493
|
+
overrides,
|
|
494
|
+
defaultBasePath,
|
|
495
|
+
requirePublicUrl,
|
|
496
|
+
});
|
|
497
|
+
if (resolution.status === "disabled") {
|
|
498
|
+
throw new Error(`Twilio phone voice transport is disabled: ${resolution.reason}`);
|
|
499
|
+
}
|
|
500
|
+
return resolution.settings;
|
|
501
|
+
}
|
|
502
|
+
async function startConfiguredTwilioPhoneTransport(options, deps = defaultTwilioPhoneTransportRuntimeDeps) {
|
|
503
|
+
let settings;
|
|
504
|
+
try {
|
|
505
|
+
settings = await readFreshRuntimeSettings(options.agentName, options.overrides, options.defaultBasePath, options.requirePublicUrl, deps);
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
if (!errorMessage(error).startsWith("Twilio phone voice transport is disabled:"))
|
|
509
|
+
throw error;
|
|
510
|
+
const reason = errorMessage(error).replace(/^Twilio phone voice transport is disabled: /, "");
|
|
511
|
+
(0, runtime_1.emitNervesEvent)({
|
|
512
|
+
component: "senses",
|
|
513
|
+
event: "senses.voice_twilio_transport_disabled",
|
|
514
|
+
message: "Twilio phone voice transport is not attached on this machine",
|
|
515
|
+
meta: { agentName: options.agentName, reason },
|
|
516
|
+
});
|
|
517
|
+
return { status: "disabled", reason };
|
|
518
|
+
}
|
|
519
|
+
await deps.cacheSelectedProviderCredentials(options.agentName);
|
|
520
|
+
if (settings.openaiRealtime?.apiKeySource === "integrations.openaiEmbeddingsApiKey") {
|
|
521
|
+
(0, runtime_1.emitNervesEvent)({
|
|
522
|
+
level: "warn",
|
|
523
|
+
component: "senses",
|
|
524
|
+
event: "senses.voice_openai_realtime_compat_key",
|
|
525
|
+
message: "OpenAI Realtime voice is temporarily using the legacy OpenAI embeddings API key",
|
|
526
|
+
meta: { agentName: settings.agentName, source: settings.openaiRealtime.apiKeySource },
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
const settingsNeedsOpenAIRealtime = settings.conversationEngine === "openai-realtime"
|
|
530
|
+
|| settings.conversationEngine === "openai-sip"
|
|
531
|
+
|| settings.outboundConversationEngine === "openai-realtime"
|
|
532
|
+
|| settings.outboundConversationEngine === "openai-sip";
|
|
533
|
+
const settingsNeedsCascade = settings.conversationEngine === "cascade" || settings.outboundConversationEngine === "cascade";
|
|
534
|
+
const transcriber = settingsNeedsOpenAIRealtime && !settingsNeedsCascade
|
|
535
|
+
? {
|
|
536
|
+
transcribe: async () => {
|
|
537
|
+
throw new Error("OpenAI Realtime voice sessions do not use the cascade transcriber");
|
|
538
|
+
},
|
|
539
|
+
}
|
|
540
|
+
: deps.createTranscriber({
|
|
541
|
+
whisperCliPath: settings.whisperCliPath,
|
|
542
|
+
modelPath: settings.whisperModelPath,
|
|
543
|
+
});
|
|
544
|
+
const tts = settingsNeedsOpenAIRealtime && !settingsNeedsCascade
|
|
545
|
+
? {
|
|
546
|
+
synthesize: async () => {
|
|
547
|
+
throw new Error("OpenAI Realtime voice sessions do not use the cascade TTS service");
|
|
548
|
+
},
|
|
549
|
+
}
|
|
550
|
+
: deps.createTts({
|
|
551
|
+
apiKey: settings.elevenLabsApiKey,
|
|
552
|
+
voiceId: settings.elevenLabsVoiceId,
|
|
553
|
+
outputFormat: settings.transportMode === "media-stream" ? "ulaw_8000" : "mp3_44100_128",
|
|
554
|
+
});
|
|
555
|
+
const bridge = await deps.startBridgeServer({
|
|
556
|
+
agentName: settings.agentName,
|
|
557
|
+
publicBaseUrl: settings.publicBaseUrl,
|
|
558
|
+
basePath: settings.basePath,
|
|
559
|
+
outputDir: settings.outputDir,
|
|
560
|
+
transcriber,
|
|
561
|
+
tts,
|
|
562
|
+
port: settings.port,
|
|
563
|
+
host: settings.host,
|
|
564
|
+
twilioAccountSid: settings.twilioAccountSid,
|
|
565
|
+
twilioAuthToken: settings.twilioAuthToken,
|
|
566
|
+
twilioFromNumber: settings.twilioFromNumber,
|
|
567
|
+
defaultFriendId: settings.defaultFriendId,
|
|
568
|
+
recordTimeoutSeconds: settings.recordTimeoutSeconds,
|
|
569
|
+
recordMaxLengthSeconds: settings.recordMaxLengthSeconds,
|
|
570
|
+
greetingPrebufferMs: settings.greetingPrebufferMs,
|
|
571
|
+
transportMode: settings.transportMode,
|
|
572
|
+
playbackMode: settings.playbackMode,
|
|
573
|
+
conversationEngine: settings.conversationEngine,
|
|
574
|
+
outboundConversationEngine: settings.outboundConversationEngine,
|
|
575
|
+
openaiRealtime: settings.openaiRealtime,
|
|
576
|
+
openaiSip: settings.openaiSip,
|
|
577
|
+
});
|
|
578
|
+
(0, runtime_1.emitNervesEvent)({
|
|
579
|
+
component: "senses",
|
|
580
|
+
event: "senses.voice_twilio_transport_ready",
|
|
581
|
+
message: "Twilio phone voice transport is ready",
|
|
582
|
+
meta: {
|
|
583
|
+
agentName: settings.agentName,
|
|
584
|
+
localUrl: bridge.localUrl,
|
|
585
|
+
publicBaseUrl: settings.publicBaseUrl,
|
|
586
|
+
basePath: settings.basePath,
|
|
587
|
+
webhookUrl: settings.webhookUrl,
|
|
588
|
+
openaiSipWebhookUrl: settings.openaiSipWebhookUrl ?? "",
|
|
589
|
+
transportMode: settings.transportMode,
|
|
590
|
+
conversationEngine: settings.conversationEngine,
|
|
591
|
+
outboundConversationEngine: settings.outboundConversationEngine,
|
|
592
|
+
openaiRealtimeModel: settings.openaiRealtime?.model ?? "",
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
return { status: "started", settings, bridge };
|
|
596
|
+
}
|
|
597
|
+
function createOutboundId(now) {
|
|
598
|
+
const timestamp = now.toISOString().replace(/[^0-9A-Za-z]+/g, "").slice(0, 15);
|
|
599
|
+
return `outbound-${timestamp}-${crypto.randomUUID().slice(0, 8)}`;
|
|
600
|
+
}
|
|
601
|
+
function terminalOutboundStatus(status) {
|
|
602
|
+
const normalized = status?.trim().toLowerCase();
|
|
603
|
+
return normalized === "completed"
|
|
604
|
+
|| normalized === "busy"
|
|
605
|
+
|| normalized === "no-answer"
|
|
606
|
+
|| normalized === "failed"
|
|
607
|
+
|| normalized === "canceled"
|
|
608
|
+
|| normalized === "voicemail"
|
|
609
|
+
|| normalized === "fax";
|
|
610
|
+
}
|
|
611
|
+
function safeRuntimeSegment(input) {
|
|
612
|
+
/* v8 ignore next -- generated outbound IDs and E.164 numbers always leave a non-empty safe segment @preserve */
|
|
613
|
+
return input.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
614
|
+
}
|
|
615
|
+
async function prewarmOutboundGreeting(options, deps) {
|
|
616
|
+
/* v8 ignore next -- defensive guard against record-play prewarm calls; the implicit-media-stream default added when realtime/SIP credentials are configured prevents this branch from being reachable in current outbound tests @preserve */
|
|
617
|
+
if (options.settings.transportMode !== "media-stream")
|
|
618
|
+
return undefined;
|
|
619
|
+
/* v8 ignore next -- Realtime/SIP outbound tests assert no cascade prewarm is attempted @preserve */
|
|
620
|
+
if (options.settings.outboundConversationEngine === "openai-realtime" || options.settings.outboundConversationEngine === "openai-sip")
|
|
621
|
+
return undefined;
|
|
622
|
+
const friendId = options.friendId?.trim() || `twilio-${safeRuntimeSegment(options.to)}`;
|
|
623
|
+
const sessionKey = (0, twilio_phone_1.twilioPhoneVoiceSessionKey)({
|
|
624
|
+
defaultFriendId: friendId,
|
|
625
|
+
from: options.to,
|
|
626
|
+
to: options.from,
|
|
627
|
+
});
|
|
628
|
+
const utteranceId = `twilio-${safeRuntimeSegment(options.outboundId)}-outbound-connected`;
|
|
629
|
+
(0, runtime_1.emitNervesEvent)({
|
|
630
|
+
component: "senses",
|
|
631
|
+
event: "senses.voice_twilio_outbound_greeting_prewarm_start",
|
|
632
|
+
message: "prewarming Twilio outbound voice greeting before dialing",
|
|
633
|
+
meta: { agentName: options.settings.agentName, outboundId: safeRuntimeSegment(options.outboundId), sessionKey },
|
|
634
|
+
});
|
|
635
|
+
const transcript = (0, transcript_1.buildVoiceTranscript)({
|
|
636
|
+
utteranceId,
|
|
637
|
+
text: (0, twilio_phone_1.outboundCallAnsweredPrompt)({
|
|
638
|
+
schemaVersion: 1,
|
|
639
|
+
outboundId: options.outboundId,
|
|
640
|
+
agentName: options.settings.agentName,
|
|
641
|
+
/* v8 ignore next -- no-friend outbound calls are covered at placement/job persistence boundary @preserve */
|
|
642
|
+
...(options.friendId?.trim() ? { friendId: options.friendId.trim() } : {}),
|
|
643
|
+
to: options.to,
|
|
644
|
+
from: options.from,
|
|
645
|
+
reason: options.reason,
|
|
646
|
+
createdAt: options.createdAt,
|
|
647
|
+
status: "prewarming",
|
|
648
|
+
}, { From: options.from, To: options.to }),
|
|
649
|
+
source: "loopback",
|
|
650
|
+
});
|
|
651
|
+
const tts = deps.createTts({
|
|
652
|
+
apiKey: options.settings.elevenLabsApiKey,
|
|
653
|
+
voiceId: options.settings.elevenLabsVoiceId,
|
|
654
|
+
outputFormat: "ulaw_8000",
|
|
655
|
+
});
|
|
656
|
+
const turn = await deps.runVoiceLoopbackTurn({
|
|
657
|
+
agentName: options.settings.agentName,
|
|
658
|
+
friendId,
|
|
659
|
+
sessionKey,
|
|
660
|
+
transcript,
|
|
661
|
+
tts,
|
|
662
|
+
});
|
|
663
|
+
if (turn.tts.status !== "delivered") {
|
|
664
|
+
throw new Error(`outbound greeting prewarm failed: ${turn.tts.error}`);
|
|
665
|
+
}
|
|
666
|
+
const playback = await deps.writeVoicePlaybackArtifact({
|
|
667
|
+
utteranceId,
|
|
668
|
+
delivery: turn.tts,
|
|
669
|
+
outputDir: path.join(options.settings.outputDir, "outbound-greetings", safeRuntimeSegment(options.outboundId)),
|
|
670
|
+
});
|
|
671
|
+
const preparedAt = new Date().toISOString();
|
|
672
|
+
(0, runtime_1.emitNervesEvent)({
|
|
673
|
+
component: "senses",
|
|
674
|
+
event: "senses.voice_twilio_outbound_greeting_prewarm_end",
|
|
675
|
+
message: "prewarmed Twilio outbound voice greeting before dialing",
|
|
676
|
+
meta: {
|
|
677
|
+
agentName: options.settings.agentName,
|
|
678
|
+
outboundId: safeRuntimeSegment(options.outboundId),
|
|
679
|
+
sessionKey,
|
|
680
|
+
byteLength: String(playback.byteLength),
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
return {
|
|
684
|
+
utteranceId,
|
|
685
|
+
audioPath: playback.audioPath,
|
|
686
|
+
mimeType: playback.mimeType,
|
|
687
|
+
byteLength: playback.byteLength,
|
|
688
|
+
preparedAt,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
async function placeConfiguredTwilioPhoneCall(options, deps = defaultTwilioPhoneOutboundCallRuntimeDeps) {
|
|
692
|
+
const settings = await readFreshRuntimeSettings(options.agentName, undefined, agentScopedTwilioPhoneBasePath(options.agentName), true, deps, { preferCached: true });
|
|
693
|
+
const to = (0, twilio_phone_1.normalizeTwilioE164PhoneNumber)(options.to);
|
|
694
|
+
const from = (0, twilio_phone_1.normalizeTwilioE164PhoneNumber)(settings.twilioFromNumber);
|
|
695
|
+
if (!to)
|
|
696
|
+
throw new Error("outbound voice call target must be an E.164 phone number");
|
|
697
|
+
if (!from)
|
|
698
|
+
throw new Error("missing voice.twilioFromNumber; save the Twilio caller number before outbound phone calls");
|
|
699
|
+
if (!settings.twilioAccountSid?.trim())
|
|
700
|
+
throw new Error("missing voice.twilioAccountSid; save Twilio credentials before outbound phone calls");
|
|
701
|
+
if (!settings.twilioAuthToken?.trim())
|
|
702
|
+
throw new Error("missing voice.twilioAuthToken; save Twilio credentials before outbound phone calls");
|
|
703
|
+
const accountSid = settings.twilioAccountSid.trim();
|
|
704
|
+
const authToken = settings.twilioAuthToken.trim();
|
|
705
|
+
const now = options.now ?? new Date();
|
|
706
|
+
const recent = await (0, twilio_phone_1.readRecentTwilioOutboundCallJobs)({
|
|
707
|
+
outputDir: settings.outputDir,
|
|
708
|
+
to,
|
|
709
|
+
friendId: options.friendId,
|
|
710
|
+
sinceMs: options.redialGuardMs ?? 120_000,
|
|
711
|
+
now: now.getTime(),
|
|
712
|
+
});
|
|
713
|
+
const activeRecent = recent.find((job) => !terminalOutboundStatus(job.status));
|
|
714
|
+
if (activeRecent) {
|
|
715
|
+
throw new Error("outbound voice call suppressed: a recent call to this friend/number is still active");
|
|
716
|
+
}
|
|
717
|
+
const outboundId = options.outboundId?.trim() || createOutboundId(now);
|
|
718
|
+
const createdAt = now.toISOString();
|
|
719
|
+
const webhookUrl = (0, twilio_phone_1.twilioOutboundCallWebhookUrl)(settings.publicBaseUrl, settings.basePath, outboundId);
|
|
720
|
+
const statusCallbackUrl = (0, twilio_phone_1.twilioOutboundCallStatusCallbackUrl)(settings.publicBaseUrl, settings.basePath, outboundId);
|
|
721
|
+
const amdStatusCallbackUrl = (0, twilio_phone_1.twilioOutboundCallAmdCallbackUrl)(settings.publicBaseUrl, settings.basePath, outboundId);
|
|
722
|
+
await (0, twilio_phone_1.writeTwilioOutboundCallJob)(settings.outputDir, {
|
|
723
|
+
schemaVersion: 1,
|
|
724
|
+
outboundId,
|
|
725
|
+
agentName: options.agentName,
|
|
726
|
+
...(options.friendId?.trim() ? { friendId: options.friendId.trim() } : {}),
|
|
727
|
+
to,
|
|
728
|
+
from,
|
|
729
|
+
reason: options.reason.trim(),
|
|
730
|
+
...(options.initialAudio ? { initialAudio: options.initialAudio } : {}),
|
|
731
|
+
createdAt,
|
|
732
|
+
status: settings.transportMode === "media-stream" && settings.outboundConversationEngine === "cascade"
|
|
733
|
+
? "prewarming"
|
|
734
|
+
: "requested",
|
|
735
|
+
});
|
|
736
|
+
try {
|
|
737
|
+
const prewarmedGreeting = await prewarmOutboundGreeting({
|
|
738
|
+
settings,
|
|
739
|
+
outboundId,
|
|
740
|
+
friendId: options.friendId,
|
|
741
|
+
to,
|
|
742
|
+
from,
|
|
743
|
+
reason: options.reason.trim(),
|
|
744
|
+
createdAt,
|
|
745
|
+
}, deps);
|
|
746
|
+
if (prewarmedGreeting) {
|
|
747
|
+
await (0, twilio_phone_1.updateTwilioOutboundCallJob)(settings.outputDir, outboundId, {
|
|
748
|
+
status: "requested",
|
|
749
|
+
prewarmedGreeting,
|
|
750
|
+
events: [{ at: prewarmedGreeting.preparedAt, status: "greeting-prewarmed" }],
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
const call = await deps.createOutboundCall({
|
|
754
|
+
accountSid,
|
|
755
|
+
authToken,
|
|
756
|
+
to,
|
|
757
|
+
from,
|
|
758
|
+
twimlUrl: webhookUrl,
|
|
759
|
+
statusCallbackUrl,
|
|
760
|
+
machineDetection: "Enable",
|
|
761
|
+
asyncAmd: true,
|
|
762
|
+
asyncAmdStatusCallbackUrl: amdStatusCallbackUrl,
|
|
763
|
+
});
|
|
764
|
+
await (0, twilio_phone_1.updateTwilioOutboundCallJob)(settings.outputDir, outboundId, {
|
|
765
|
+
transportCallSid: call.callSid,
|
|
766
|
+
status: call.status ?? "queued",
|
|
767
|
+
events: [
|
|
768
|
+
...(prewarmedGreeting ? [{ at: prewarmedGreeting.preparedAt, status: "greeting-prewarmed" }] : []),
|
|
769
|
+
{ at: new Date().toISOString(), status: call.status ?? "queued", ...(call.callSid ? { callSid: call.callSid } : {}) },
|
|
770
|
+
],
|
|
771
|
+
});
|
|
772
|
+
(0, runtime_1.emitNervesEvent)({
|
|
773
|
+
component: "senses",
|
|
774
|
+
event: "senses.voice_twilio_outbound_call_requested",
|
|
775
|
+
message: "Twilio outbound voice call requested",
|
|
776
|
+
meta: {
|
|
777
|
+
agentName: options.agentName,
|
|
778
|
+
outboundId: outboundId.replace(/[^A-Za-z0-9._-]+/g, "-"),
|
|
779
|
+
callSid: call.callSid?.replace(/[^A-Za-z0-9._-]+/g, "-") ?? "unknown",
|
|
780
|
+
status: call.status ?? "queued",
|
|
781
|
+
},
|
|
782
|
+
});
|
|
783
|
+
return {
|
|
784
|
+
outboundId,
|
|
785
|
+
callSid: call.callSid,
|
|
786
|
+
status: call.status,
|
|
787
|
+
webhookUrl,
|
|
788
|
+
statusCallbackUrl,
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
await (0, twilio_phone_1.updateTwilioOutboundCallJob)(settings.outputDir, outboundId, {
|
|
793
|
+
status: "failed",
|
|
794
|
+
error: errorMessage(error),
|
|
795
|
+
});
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function closeTwilioPhoneBridgeServer(server) {
|
|
800
|
+
return Promise.all([
|
|
801
|
+
server.bridge.close?.() ?? Promise.resolve(),
|
|
802
|
+
new Promise((resolve, reject) => {
|
|
803
|
+
;
|
|
804
|
+
server.server.close((error) => error ? reject(error) : resolve());
|
|
805
|
+
}),
|
|
806
|
+
]).then(() => undefined);
|
|
807
|
+
}
|