@vellumai/assistant 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +4 -5
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +1 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +3 -3
- package/src/__tests__/context-window-manager.test.ts +78 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +117 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-regressions.test.ts +8 -30
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/require-fresh-approval.test.ts +4 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/tool-executor.test.ts +4 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/conversations.ts +0 -18
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +16 -16
- package/src/config/feature-flag-registry.json +48 -16
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -25
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/platform.ts +1 -1
- package/src/config/schemas/security.ts +4 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/context/window-manager.ts +53 -2
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +6 -4
- package/src/daemon/conversation-agent-loop.ts +0 -60
- package/src/daemon/conversation-memory.ts +0 -117
- package/src/daemon/conversation-runtime-assembly.ts +0 -2
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/conversations.ts +0 -11
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +229 -96
- package/src/daemon/message-types/conversations.ts +3 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/followups/followup-store.ts +5 -2
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-crud.ts +0 -236
- package/src/memory/conversation-title-service.ts +76 -11
- package/src/memory/db-init.ts +15 -11
- package/src/memory/indexer.ts +15 -106
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/embedding.ts +0 -79
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +30 -13
- package/src/memory/jobs-worker.ts +31 -27
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/index.ts +5 -3
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-client.ts +4 -6
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/conversations.ts +0 -3
- package/src/memory/schema/index.ts +0 -2
- package/src/messaging/draft-store.ts +2 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/defaults.ts +3 -3
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/trust-client.ts +2 -13
- package/src/permissions/trust-store.ts +8 -3
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +29 -1
- package/src/runtime/auth/token-service.ts +53 -15
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +29 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-management-routes.ts +0 -36
- package/src/runtime/routes/conversation-query-routes.ts +106 -2
- package/src/runtime/routes/conversation-routes.ts +4 -43
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +221 -3
- package/src/runtime/routes/memory-item-routes.ts +144 -4
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/schedule/schedule-store.ts +0 -21
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/skills/inline-command-render.ts +5 -1
- package/src/skills/inline-command-runner.ts +30 -2
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/handlers.ts +1 -129
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +9 -2
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +8 -55
- package/src/util/xml.ts +8 -0
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/heartbeat-service.ts +5 -24
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/archive-recall.test.ts +0 -560
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
- package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
- package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
- package/src/__tests__/memory-brief-time.test.ts +0 -285
- package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
- package/src/__tests__/memory-chunk-archive.test.ts +0 -400
- package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
- package/src/__tests__/memory-episode-archive.test.ts +0 -370
- package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
- package/src/__tests__/memory-observation-archive.test.ts +0 -375
- package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
- package/src/__tests__/memory-reducer-job.test.ts +0 -538
- package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
- package/src/__tests__/memory-reducer-store.test.ts +0 -728
- package/src/__tests__/memory-reducer-types.test.ts +0 -707
- package/src/__tests__/memory-reducer.test.ts +0 -704
- package/src/__tests__/memory-simplified-config.test.ts +0 -281
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
- package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/memory-simplified.ts +0 -101
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/archive-recall.ts +0 -516
- package/src/memory/archive-store.ts +0 -400
- package/src/memory/brief-formatting.ts +0 -33
- package/src/memory/brief-open-loops.ts +0 -266
- package/src/memory/brief-time.ts +0 -162
- package/src/memory/brief.ts +0 -75
- package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
- package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
- package/src/memory/migrations/185-memory-brief-state.ts +0 -52
- package/src/memory/migrations/186-memory-archive.ts +0 -109
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
- package/src/memory/reducer-scheduler.ts +0 -242
- package/src/memory/reducer-store.ts +0 -271
- package/src/memory/reducer-types.ts +0 -106
- package/src/memory/reducer.ts +0 -467
- package/src/memory/schema/memory-archive.ts +0 -121
- package/src/memory/schema/memory-brief.ts +0 -55
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migration rollback endpoint — rolls back DB and/or workspace migrations
|
|
3
|
+
* to a specified target version/migration ID.
|
|
4
|
+
*
|
|
5
|
+
* Protected by a route policy restricting access to gateway service
|
|
6
|
+
* principals only (`svc_gateway` with `internal.write` scope), following
|
|
7
|
+
* the same pattern as other gateway-forwarded control-plane endpoints.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getDb } from "../../memory/db-connection.js";
|
|
11
|
+
import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
|
|
12
|
+
import { rollbackMemoryMigration } from "../../memory/migrations/validate-migration-state.js";
|
|
13
|
+
import { getWorkspaceDir } from "../../util/platform.js";
|
|
14
|
+
import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
|
|
15
|
+
import {
|
|
16
|
+
getLastWorkspaceMigrationId,
|
|
17
|
+
loadCheckpoints,
|
|
18
|
+
rollbackWorkspaceMigrations,
|
|
19
|
+
} from "../../workspace/migrations/runner.js";
|
|
20
|
+
import { httpError } from "../http-errors.js";
|
|
21
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
22
|
+
|
|
23
|
+
export function migrationRollbackRouteDefinitions(): RouteDefinition[] {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
endpoint: "admin/rollback-migrations",
|
|
27
|
+
method: "POST",
|
|
28
|
+
handler: async ({ req }) => {
|
|
29
|
+
let body: unknown;
|
|
30
|
+
try {
|
|
31
|
+
body = await req.json();
|
|
32
|
+
} catch {
|
|
33
|
+
return httpError("BAD_REQUEST", "Invalid JSON body", 400);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!body || typeof body !== "object") {
|
|
37
|
+
return httpError(
|
|
38
|
+
"BAD_REQUEST",
|
|
39
|
+
"Request body must be a JSON object",
|
|
40
|
+
400,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
targetDbVersion,
|
|
46
|
+
targetWorkspaceMigrationId,
|
|
47
|
+
rollbackToRegistryCeiling,
|
|
48
|
+
} = body as {
|
|
49
|
+
targetDbVersion?: unknown;
|
|
50
|
+
targetWorkspaceMigrationId?: unknown;
|
|
51
|
+
rollbackToRegistryCeiling?: unknown;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// When rollbackToRegistryCeiling is true, auto-determine targets
|
|
55
|
+
// from this daemon's own migration registry ceilings.
|
|
56
|
+
let effectiveDbVersion = targetDbVersion as number | undefined;
|
|
57
|
+
let effectiveWorkspaceMigrationId = targetWorkspaceMigrationId as
|
|
58
|
+
| string
|
|
59
|
+
| undefined;
|
|
60
|
+
|
|
61
|
+
if (rollbackToRegistryCeiling === true) {
|
|
62
|
+
if (effectiveDbVersion === undefined)
|
|
63
|
+
effectiveDbVersion = getMaxMigrationVersion();
|
|
64
|
+
if (effectiveWorkspaceMigrationId === undefined)
|
|
65
|
+
effectiveWorkspaceMigrationId =
|
|
66
|
+
getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS) ?? undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// At least one rollback target must be specified.
|
|
70
|
+
if (
|
|
71
|
+
effectiveDbVersion === undefined &&
|
|
72
|
+
effectiveWorkspaceMigrationId === undefined
|
|
73
|
+
) {
|
|
74
|
+
return httpError(
|
|
75
|
+
"BAD_REQUEST",
|
|
76
|
+
"At least one of targetDbVersion or targetWorkspaceMigrationId must be provided",
|
|
77
|
+
400,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Validate effectiveDbVersion when provided.
|
|
82
|
+
if (effectiveDbVersion !== undefined) {
|
|
83
|
+
if (
|
|
84
|
+
typeof effectiveDbVersion !== "number" ||
|
|
85
|
+
!Number.isInteger(effectiveDbVersion) ||
|
|
86
|
+
effectiveDbVersion < 0
|
|
87
|
+
) {
|
|
88
|
+
return httpError(
|
|
89
|
+
"BAD_REQUEST",
|
|
90
|
+
"targetDbVersion must be a non-negative integer",
|
|
91
|
+
400,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Validate effectiveWorkspaceMigrationId when provided.
|
|
97
|
+
if (effectiveWorkspaceMigrationId !== undefined) {
|
|
98
|
+
if (
|
|
99
|
+
typeof effectiveWorkspaceMigrationId !== "string" ||
|
|
100
|
+
effectiveWorkspaceMigrationId.length === 0
|
|
101
|
+
) {
|
|
102
|
+
return httpError(
|
|
103
|
+
"BAD_REQUEST",
|
|
104
|
+
"targetWorkspaceMigrationId must be a non-empty string",
|
|
105
|
+
400,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Preflight: validate that the workspace migration ID exists in the
|
|
111
|
+
// registry BEFORE executing any mutations. This prevents the DB
|
|
112
|
+
// rollback from committing when the workspace target is invalid.
|
|
113
|
+
let resolvedTargetIndex = -1;
|
|
114
|
+
if (effectiveWorkspaceMigrationId !== undefined) {
|
|
115
|
+
const targetId = effectiveWorkspaceMigrationId as string;
|
|
116
|
+
resolvedTargetIndex = WORKSPACE_MIGRATIONS.findIndex(
|
|
117
|
+
(m) => m.id === targetId,
|
|
118
|
+
);
|
|
119
|
+
if (resolvedTargetIndex === -1) {
|
|
120
|
+
return httpError(
|
|
121
|
+
"BAD_REQUEST",
|
|
122
|
+
`Target workspace migration "${targetId}" not found in the registry`,
|
|
123
|
+
400,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const rolledBack: { db: string[]; workspace: string[] } = {
|
|
129
|
+
db: [],
|
|
130
|
+
workspace: [],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Roll back DB migrations if requested.
|
|
134
|
+
if (effectiveDbVersion !== undefined) {
|
|
135
|
+
try {
|
|
136
|
+
rolledBack.db = rollbackMemoryMigration(
|
|
137
|
+
getDb(),
|
|
138
|
+
effectiveDbVersion,
|
|
139
|
+
);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
const detail = err instanceof Error ? err.message : "Unknown error";
|
|
142
|
+
return httpError(
|
|
143
|
+
"INTERNAL_ERROR",
|
|
144
|
+
`DB migration rollback failed: ${detail}`,
|
|
145
|
+
500,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Roll back workspace migrations if requested.
|
|
151
|
+
if (effectiveWorkspaceMigrationId !== undefined) {
|
|
152
|
+
const workspaceDir = getWorkspaceDir();
|
|
153
|
+
|
|
154
|
+
// Compute which migrations are candidates for rollback before
|
|
155
|
+
// executing, since rollbackWorkspaceMigrations returns void.
|
|
156
|
+
const targetId = effectiveWorkspaceMigrationId;
|
|
157
|
+
|
|
158
|
+
const checkpointsBefore = loadCheckpoints(workspaceDir);
|
|
159
|
+
const candidateIds = WORKSPACE_MIGRATIONS.slice(
|
|
160
|
+
resolvedTargetIndex + 1,
|
|
161
|
+
)
|
|
162
|
+
.filter((m) => {
|
|
163
|
+
const entry = checkpointsBefore.applied[m.id];
|
|
164
|
+
return (
|
|
165
|
+
entry &&
|
|
166
|
+
entry.status !== "started" &&
|
|
167
|
+
entry.status !== "rolling_back"
|
|
168
|
+
);
|
|
169
|
+
})
|
|
170
|
+
.map((m) => m.id);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await rollbackWorkspaceMigrations(
|
|
174
|
+
workspaceDir,
|
|
175
|
+
WORKSPACE_MIGRATIONS,
|
|
176
|
+
targetId,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
rolledBack.workspace = candidateIds;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
// Re-read checkpoints to determine which migrations were actually
|
|
182
|
+
// rolled back before the error occurred. A candidate whose entry
|
|
183
|
+
// is no longer present in the checkpoint file was successfully
|
|
184
|
+
// reverted.
|
|
185
|
+
const checkpointsAfter = loadCheckpoints(workspaceDir);
|
|
186
|
+
const actuallyRolledBack = candidateIds.filter(
|
|
187
|
+
(id) => !checkpointsAfter.applied[id],
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const detail = err instanceof Error ? err.message : "Unknown error";
|
|
191
|
+
return httpError(
|
|
192
|
+
"INTERNAL_ERROR",
|
|
193
|
+
`Workspace migration rollback failed: ${detail}`,
|
|
194
|
+
500,
|
|
195
|
+
{
|
|
196
|
+
partialRolledBack: {
|
|
197
|
+
db: rolledBack.db,
|
|
198
|
+
workspace: actuallyRolledBack,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return Response.json({ ok: true, rolledBack });
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
}
|
|
@@ -15,7 +15,8 @@ import { join } from "node:path";
|
|
|
15
15
|
import { Database } from "bun:sqlite";
|
|
16
16
|
|
|
17
17
|
import { invalidateConfigCache } from "../../config/loader.js";
|
|
18
|
-
import { resetDb } from "../../memory/db-connection.js";
|
|
18
|
+
import { getDb, resetDb } from "../../memory/db-connection.js";
|
|
19
|
+
import { validateMigrationState } from "../../memory/migrations/validate-migration-state.js";
|
|
19
20
|
import { clearCache as clearTrustCache } from "../../permissions/trust-store.js";
|
|
20
21
|
import { getLogger } from "../../util/logger.js";
|
|
21
22
|
import {
|
|
@@ -438,6 +439,21 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
|
|
|
438
439
|
invalidateConfigCache();
|
|
439
440
|
clearTrustCache();
|
|
440
441
|
|
|
442
|
+
// Check whether the imported database contains migration checkpoints from
|
|
443
|
+
// a newer version. This is non-blocking — the import has already
|
|
444
|
+
// succeeded — but we surface a warning so the caller knows some data may
|
|
445
|
+
// not be fully compatible with this daemon's schema.
|
|
446
|
+
try {
|
|
447
|
+
const migrationValidation = validateMigrationState(getDb());
|
|
448
|
+
if (migrationValidation.unknownCheckpoints.length > 0) {
|
|
449
|
+
result.report.warnings.push(
|
|
450
|
+
`Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
} catch {
|
|
454
|
+
// Don't fail the import if validation itself errors
|
|
455
|
+
}
|
|
456
|
+
|
|
441
457
|
return Response.json(result.report);
|
|
442
458
|
} catch (err) {
|
|
443
459
|
log.error({ err }, "Unexpected error during import commit");
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route handlers for notification delivery acknowledgments.
|
|
3
|
+
*
|
|
4
|
+
* Provides a REST endpoint for clients to report the outcome of
|
|
5
|
+
* local notification delivery (UNUserNotificationCenter.add).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { eq } from "drizzle-orm";
|
|
9
|
+
|
|
10
|
+
import { getDb } from "../../memory/db.js";
|
|
11
|
+
import { notificationDeliveries } from "../../memory/schema.js";
|
|
12
|
+
import { httpError } from "../http-errors.js";
|
|
13
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
14
|
+
|
|
15
|
+
export function notificationRouteDefinitions(): RouteDefinition[] {
|
|
16
|
+
return [
|
|
17
|
+
// POST /v1/notification-intent-result — client ack for notification delivery
|
|
18
|
+
{
|
|
19
|
+
endpoint: "notification-intent-result",
|
|
20
|
+
method: "POST",
|
|
21
|
+
policyKey: "notification-intent-result",
|
|
22
|
+
handler: async ({ req }) => {
|
|
23
|
+
const body = (await req.json()) as {
|
|
24
|
+
deliveryId?: string;
|
|
25
|
+
success?: boolean;
|
|
26
|
+
errorMessage?: string;
|
|
27
|
+
errorCode?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (!body.deliveryId || typeof body.deliveryId !== "string") {
|
|
31
|
+
return httpError("BAD_REQUEST", "deliveryId is required", 400);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const db = getDb();
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
|
|
37
|
+
const updates: Record<string, unknown> = {
|
|
38
|
+
clientDeliveryStatus: body.success ? "delivered" : "client_failed",
|
|
39
|
+
clientDeliveryAt: now,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
};
|
|
42
|
+
if (body.errorMessage) {
|
|
43
|
+
updates.clientDeliveryError = body.errorMessage;
|
|
44
|
+
}
|
|
45
|
+
if (body.errorCode) {
|
|
46
|
+
updates.errorCode = body.errorCode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
db.update(notificationDeliveries)
|
|
50
|
+
.set(updates)
|
|
51
|
+
.where(eq(notificationDeliveries.id, body.deliveryId))
|
|
52
|
+
.run();
|
|
53
|
+
|
|
54
|
+
return Response.json({ ok: true });
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
}
|
|
@@ -99,6 +99,59 @@ function handleCancelSchedule(id: string): Response {
|
|
|
99
99
|
return handleListSchedules();
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
const VALID_MODES = ["notify", "execute"] as const;
|
|
103
|
+
const VALID_ROUTING_INTENTS = [
|
|
104
|
+
"single_channel",
|
|
105
|
+
"multi_channel",
|
|
106
|
+
"all_channels",
|
|
107
|
+
] as const;
|
|
108
|
+
|
|
109
|
+
function handleUpdateSchedule(
|
|
110
|
+
id: string,
|
|
111
|
+
body: Record<string, unknown>,
|
|
112
|
+
): Response {
|
|
113
|
+
const updates: Record<string, unknown> = {};
|
|
114
|
+
|
|
115
|
+
if ("mode" in body && !VALID_MODES.includes(body.mode as (typeof VALID_MODES)[number])) {
|
|
116
|
+
return httpError("BAD_REQUEST", `Invalid mode: must be one of ${VALID_MODES.join(", ")}`, 400);
|
|
117
|
+
}
|
|
118
|
+
if ("routingIntent" in body && !VALID_ROUTING_INTENTS.includes(body.routingIntent as (typeof VALID_ROUTING_INTENTS)[number])) {
|
|
119
|
+
return httpError("BAD_REQUEST", `Invalid routingIntent: must be one of ${VALID_ROUTING_INTENTS.join(", ")}`, 400);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (const key of [
|
|
123
|
+
"name",
|
|
124
|
+
"expression",
|
|
125
|
+
"timezone",
|
|
126
|
+
"message",
|
|
127
|
+
"mode",
|
|
128
|
+
"routingIntent",
|
|
129
|
+
"quiet",
|
|
130
|
+
] as const) {
|
|
131
|
+
if (key in body) {
|
|
132
|
+
updates[key] = body[key];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const updated = updateSchedule(id, updates);
|
|
138
|
+
if (!updated) {
|
|
139
|
+
return httpError("NOT_FOUND", "Schedule not found", 404);
|
|
140
|
+
}
|
|
141
|
+
log.info({ id, updates }, "Schedule updated via HTTP");
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (
|
|
144
|
+
err instanceof Error &&
|
|
145
|
+
(err.message.includes("Invalid") || err.message.includes("invalid"))
|
|
146
|
+
) {
|
|
147
|
+
return httpError("BAD_REQUEST", err.message, 400);
|
|
148
|
+
}
|
|
149
|
+
log.error({ err }, "Failed to update schedule");
|
|
150
|
+
return httpError("INTERNAL_ERROR", "Failed to update schedule", 500);
|
|
151
|
+
}
|
|
152
|
+
return handleListSchedules();
|
|
153
|
+
}
|
|
154
|
+
|
|
102
155
|
async function handleRunScheduleNow(
|
|
103
156
|
id: string,
|
|
104
157
|
sendMessageDeps?: SendMessageDeps,
|
|
@@ -243,6 +296,18 @@ export function scheduleRouteDefinitions(deps: {
|
|
|
243
296
|
policyKey: "schedules",
|
|
244
297
|
handler: ({ params }) => handleDeleteSchedule(params.id),
|
|
245
298
|
},
|
|
299
|
+
{
|
|
300
|
+
endpoint: "schedules/:id",
|
|
301
|
+
method: "PATCH",
|
|
302
|
+
policyKey: "schedules",
|
|
303
|
+
handler: async ({ req, params }) => {
|
|
304
|
+
const body: unknown = await req.json();
|
|
305
|
+
if (typeof body !== "object" || !body || Array.isArray(body)) {
|
|
306
|
+
return httpError("BAD_REQUEST", "Request body must be a JSON object", 400);
|
|
307
|
+
}
|
|
308
|
+
return handleUpdateSchedule(params.id, body as Record<string, unknown>);
|
|
309
|
+
},
|
|
310
|
+
},
|
|
246
311
|
{
|
|
247
312
|
endpoint: "schedules/:id/run",
|
|
248
313
|
method: "POST",
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getPlatformBaseUrl,
|
|
15
|
+
setIngressPublicBaseUrl,
|
|
16
|
+
} from "../../config/env.js";
|
|
14
17
|
import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
|
|
15
18
|
import { loadSkillCatalog } from "../../config/skills.js";
|
|
16
19
|
import {
|
|
@@ -728,6 +731,43 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
|
|
|
728
731
|
handler: () => handleEnvVars(),
|
|
729
732
|
},
|
|
730
733
|
|
|
734
|
+
// Platform config (GET / PUT)
|
|
735
|
+
{
|
|
736
|
+
endpoint: "config/platform",
|
|
737
|
+
method: "GET",
|
|
738
|
+
policyKey: "config/platform:GET",
|
|
739
|
+
handler: () => {
|
|
740
|
+
const raw = loadRawConfig();
|
|
741
|
+
const platform = (raw?.platform ?? {}) as Record<string, unknown>;
|
|
742
|
+
const baseUrl =
|
|
743
|
+
(platform.baseUrl as string | undefined) || getPlatformBaseUrl();
|
|
744
|
+
return Response.json({ baseUrl, success: true });
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
endpoint: "config/platform",
|
|
749
|
+
method: "PUT",
|
|
750
|
+
policyKey: "config/platform",
|
|
751
|
+
handler: async ({ req }) => {
|
|
752
|
+
try {
|
|
753
|
+
const body = (await req.json()) as { baseUrl?: string };
|
|
754
|
+
const value = (body.baseUrl ?? "").trim().replace(/\/+$/, "");
|
|
755
|
+
const raw = loadRawConfig();
|
|
756
|
+
const platform = (raw?.platform ?? {}) as Record<string, unknown>;
|
|
757
|
+
platform.baseUrl = value || undefined;
|
|
758
|
+
saveRawConfig({ ...raw, platform });
|
|
759
|
+
return Response.json({ baseUrl: value, success: true });
|
|
760
|
+
} catch (err) {
|
|
761
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
762
|
+
log.error({ err }, "Failed to update platform config via HTTP");
|
|
763
|
+
return Response.json(
|
|
764
|
+
{ baseUrl: "", success: false, error: message },
|
|
765
|
+
{ status: 500 },
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
|
|
731
771
|
// Ingress config (GET / PUT)
|
|
732
772
|
{
|
|
733
773
|
endpoint: "integrations/ingress/config",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP route definitions for message text-to-speech synthesis.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/messages/:id/tts?conversationId=... — synthesize message text to audio
|
|
5
|
+
*
|
|
6
|
+
* Gated behind the `feature_flags.message-tts.enabled` assistant feature flag.
|
|
7
|
+
* Uses Fish Audio for synthesis when configured.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { synthesizeWithFishAudio } from "../../calls/fish-audio-client.js";
|
|
11
|
+
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
12
|
+
import { getConfig } from "../../config/loader.js";
|
|
13
|
+
import { getMessageContent } from "../../daemon/handlers/conversation-history.js";
|
|
14
|
+
import { getLogger } from "../../util/logger.js";
|
|
15
|
+
import { httpError } from "../http-errors.js";
|
|
16
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
17
|
+
|
|
18
|
+
const log = getLogger("tts-routes");
|
|
19
|
+
|
|
20
|
+
const MESSAGE_TTS_FLAG = "feature_flags.message-tts.enabled" as const;
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Route definitions
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
export function ttsRouteDefinitions(): RouteDefinition[] {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
endpoint: "messages/:id/tts",
|
|
30
|
+
method: "POST",
|
|
31
|
+
policyKey: "messages/tts",
|
|
32
|
+
handler: async ({ url, params }) => {
|
|
33
|
+
const config = getConfig();
|
|
34
|
+
|
|
35
|
+
if (!isAssistantFeatureFlagEnabled(MESSAGE_TTS_FLAG, config)) {
|
|
36
|
+
return httpError("FORBIDDEN", "Message TTS is not enabled", 403);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const messageId = params.id;
|
|
40
|
+
const conversationId =
|
|
41
|
+
url.searchParams.get("conversationId") ?? undefined;
|
|
42
|
+
|
|
43
|
+
const result = getMessageContent(messageId, conversationId);
|
|
44
|
+
if (!result) {
|
|
45
|
+
return httpError("NOT_FOUND", `Message ${messageId} not found`, 404);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!result.text) {
|
|
49
|
+
return httpError("BAD_REQUEST", "Message has no text content", 400);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { fishAudio } = config;
|
|
53
|
+
if (!fishAudio?.referenceId) {
|
|
54
|
+
return httpError(
|
|
55
|
+
"SERVICE_UNAVAILABLE",
|
|
56
|
+
"Fish Audio TTS is not configured",
|
|
57
|
+
503,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const audioBuffer = await synthesizeWithFishAudio(
|
|
63
|
+
result.text,
|
|
64
|
+
fishAudio,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const format = fishAudio.format ?? "mp3";
|
|
68
|
+
const contentType =
|
|
69
|
+
format === "wav"
|
|
70
|
+
? "audio/wav"
|
|
71
|
+
: format === "opus"
|
|
72
|
+
? "audio/opus"
|
|
73
|
+
: "audio/mpeg";
|
|
74
|
+
|
|
75
|
+
return new Response(new Uint8Array(audioBuffer), {
|
|
76
|
+
status: 200,
|
|
77
|
+
headers: { "Content-Type": contentType },
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
log.error({ err, messageId }, "TTS synthesis failed");
|
|
81
|
+
return httpError("INTERNAL_ERROR", "TTS synthesis failed", 502);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade broadcast endpoint — publishes service group update lifecycle
|
|
3
|
+
* events (starting / progress / complete) to all connected SSE clients.
|
|
4
|
+
*
|
|
5
|
+
* Protected by a route policy restricting access to gateway service
|
|
6
|
+
* principals only (`svc_gateway` with `internal.write` scope), following
|
|
7
|
+
* the same pattern as other gateway-forwarded control-plane endpoints.
|
|
8
|
+
* The gateway requires a valid edge JWT and forwards the request with a
|
|
9
|
+
* minted service token.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
ServiceGroupUpdateComplete,
|
|
14
|
+
ServiceGroupUpdateProgress,
|
|
15
|
+
ServiceGroupUpdateStarting,
|
|
16
|
+
} from "../../daemon/message-types/upgrades.js";
|
|
17
|
+
import { buildAssistantEvent } from "../assistant-event.js";
|
|
18
|
+
import { assistantEventHub } from "../assistant-event-hub.js";
|
|
19
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
20
|
+
import { httpError } from "../http-errors.js";
|
|
21
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
22
|
+
|
|
23
|
+
export function upgradeBroadcastRouteDefinitions(): RouteDefinition[] {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
endpoint: "admin/upgrade-broadcast",
|
|
27
|
+
method: "POST",
|
|
28
|
+
handler: async ({ req }) => {
|
|
29
|
+
let body: unknown;
|
|
30
|
+
try {
|
|
31
|
+
body = await req.json();
|
|
32
|
+
} catch {
|
|
33
|
+
return httpError("BAD_REQUEST", "Invalid JSON body", 400);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!body || typeof body !== "object") {
|
|
37
|
+
return httpError(
|
|
38
|
+
"BAD_REQUEST",
|
|
39
|
+
"Request body must be a JSON object",
|
|
40
|
+
400,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { type } = body as { type?: unknown };
|
|
45
|
+
|
|
46
|
+
if (type === "starting") {
|
|
47
|
+
const { targetVersion, expectedDowntimeSeconds } = body as {
|
|
48
|
+
targetVersion?: unknown;
|
|
49
|
+
expectedDowntimeSeconds?: unknown;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (typeof targetVersion !== "string" || targetVersion.length === 0) {
|
|
53
|
+
return httpError(
|
|
54
|
+
"BAD_REQUEST",
|
|
55
|
+
"targetVersion is required and must be a non-empty string",
|
|
56
|
+
400,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const downtime =
|
|
61
|
+
expectedDowntimeSeconds === undefined
|
|
62
|
+
? 60
|
|
63
|
+
: expectedDowntimeSeconds;
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
typeof downtime !== "number" ||
|
|
67
|
+
!isFinite(downtime) ||
|
|
68
|
+
downtime < 0
|
|
69
|
+
) {
|
|
70
|
+
return httpError(
|
|
71
|
+
"BAD_REQUEST",
|
|
72
|
+
"expectedDowntimeSeconds must be a non-negative number",
|
|
73
|
+
400,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const message: ServiceGroupUpdateStarting = {
|
|
78
|
+
type: "service_group_update_starting",
|
|
79
|
+
targetVersion,
|
|
80
|
+
expectedDowntimeSeconds: downtime,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
await assistantEventHub.publish(
|
|
84
|
+
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return Response.json({ ok: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (type === "progress") {
|
|
91
|
+
const { statusMessage } = body as { statusMessage?: unknown };
|
|
92
|
+
|
|
93
|
+
if (typeof statusMessage !== "string" || statusMessage.length === 0) {
|
|
94
|
+
return httpError(
|
|
95
|
+
"BAD_REQUEST",
|
|
96
|
+
"statusMessage is required and must be a non-empty string",
|
|
97
|
+
400,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const message: ServiceGroupUpdateProgress = {
|
|
102
|
+
type: "service_group_update_progress",
|
|
103
|
+
statusMessage,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await assistantEventHub.publish(
|
|
107
|
+
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return Response.json({ ok: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (type === "complete") {
|
|
114
|
+
const { installedVersion, success, rolledBackToVersion } = body as {
|
|
115
|
+
installedVersion?: unknown;
|
|
116
|
+
success?: unknown;
|
|
117
|
+
rolledBackToVersion?: unknown;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
typeof installedVersion !== "string" ||
|
|
122
|
+
installedVersion.length === 0
|
|
123
|
+
) {
|
|
124
|
+
return httpError(
|
|
125
|
+
"BAD_REQUEST",
|
|
126
|
+
"installedVersion is required and must be a non-empty string",
|
|
127
|
+
400,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (typeof success !== "boolean") {
|
|
132
|
+
return httpError(
|
|
133
|
+
"BAD_REQUEST",
|
|
134
|
+
"success is required and must be a boolean",
|
|
135
|
+
400,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
rolledBackToVersion !== undefined &&
|
|
141
|
+
(typeof rolledBackToVersion !== "string" ||
|
|
142
|
+
rolledBackToVersion.length === 0)
|
|
143
|
+
) {
|
|
144
|
+
return httpError(
|
|
145
|
+
"BAD_REQUEST",
|
|
146
|
+
"rolledBackToVersion must be a non-empty string when provided",
|
|
147
|
+
400,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const message: ServiceGroupUpdateComplete = {
|
|
152
|
+
type: "service_group_update_complete",
|
|
153
|
+
installedVersion,
|
|
154
|
+
success,
|
|
155
|
+
...(typeof rolledBackToVersion === "string"
|
|
156
|
+
? { rolledBackToVersion }
|
|
157
|
+
: {}),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
await assistantEventHub.publish(
|
|
161
|
+
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return Response.json({ ok: true });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return httpError(
|
|
168
|
+
"BAD_REQUEST",
|
|
169
|
+
'type must be "starting", "progress", or "complete"',
|
|
170
|
+
400,
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
}
|