@vellumai/assistant 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +2 -0
- package/README.md +88 -2
- package/eslint.config.mjs +31 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
- package/scripts/ipc/generate-swift.ts +31 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
- package/src/__tests__/approval-conversation-turn.test.ts +214 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/browser-manager.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +130 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +799 -249
- package/src/__tests__/call-pointer-messages.test.ts +148 -0
- package/src/__tests__/call-recovery.test.ts +3 -0
- package/src/__tests__/call-routes-http.test.ts +32 -2
- package/src/__tests__/call-store.test.ts +3 -0
- package/src/__tests__/channel-approval-routes.test.ts +1277 -98
- package/src/__tests__/channel-approval.test.ts +37 -0
- package/src/__tests__/channel-approvals.test.ts +36 -50
- package/src/__tests__/channel-guardian.test.ts +630 -22
- package/src/__tests__/channel-readiness-service.test.ts +324 -0
- package/src/__tests__/checker.test.ts +14 -7
- package/src/__tests__/clarification-resolver.test.ts +44 -24
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
- package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
- package/src/__tests__/config-schema.test.ts +14 -8
- package/src/__tests__/context-window-manager.test.ts +30 -2
- package/src/__tests__/contradiction-checker.test.ts +20 -5
- package/src/__tests__/credential-security-invariants.test.ts +7 -2
- package/src/__tests__/daemon-lifecycle.test.ts +13 -12
- package/src/__tests__/db-migration-rollback.test.ts +752 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/fuzzy-match-property.test.ts +5 -5
- package/src/__tests__/guardian-action-store.test.ts +123 -0
- package/src/__tests__/guardian-action-sweep.test.ts +277 -0
- package/src/__tests__/guardian-dispatch.test.ts +389 -0
- package/src/__tests__/guardian-question-copy.test.ts +47 -0
- package/src/__tests__/handlers-telegram-config.test.ts +4 -2
- package/src/__tests__/handlers-twilio-config.test.ts +533 -0
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +291 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/model-intents.test.ts +96 -0
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
- package/src/__tests__/provider-error-scenarios.test.ts +621 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
- package/src/__tests__/qdrant-manager.test.ts +27 -20
- package/src/__tests__/relay-server.test.ts +779 -40
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
- package/src/__tests__/run-orchestrator.test.ts +42 -4
- package/src/__tests__/runtime-runs-http.test.ts +17 -1
- package/src/__tests__/runtime-runs.test.ts +16 -0
- package/src/__tests__/schedule-store.test.ts +18 -4
- package/src/__tests__/scheduler-recurrence.test.ts +13 -4
- package/src/__tests__/session-abort-tool-results.test.ts +6 -0
- package/src/__tests__/session-agent-loop.test.ts +857 -0
- package/src/__tests__/session-conflict-gate.test.ts +6 -0
- package/src/__tests__/session-pre-run-repair.test.ts +6 -0
- package/src/__tests__/session-profile-injection.test.ts +6 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/session-queue.test.ts +6 -0
- package/src/__tests__/session-runtime-assembly.test.ts +321 -13
- package/src/__tests__/session-slash-known.test.ts +6 -0
- package/src/__tests__/session-slash-queue.test.ts +6 -0
- package/src/__tests__/session-slash-unknown.test.ts +6 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/session-workspace-injection.test.ts +6 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/skills.test.ts +2 -0
- package/src/__tests__/sms-messaging-provider.test.ts +126 -0
- package/src/__tests__/starter-task-flow.test.ts +2 -0
- package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +2 -0
- package/src/__tests__/task-management-tools.test.ts +2 -2
- package/src/__tests__/task-runner.test.ts +14 -4
- package/src/__tests__/terminal-tools.test.ts +25 -19
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
- package/src/__tests__/tool-executor.test.ts +23 -24
- package/src/__tests__/trust-store.test.ts +3 -3
- package/src/__tests__/twilio-rest.test.ts +29 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
- package/src/__tests__/twilio-routes.test.ts +167 -11
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +2 -0
- package/src/__tests__/voice-quality.test.ts +222 -0
- package/src/__tests__/web-search.test.ts +46 -30
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/agent/loop.ts +1 -1
- package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
- package/src/amazon/client.ts +1418 -0
- package/src/amazon/request-extractor.ts +135 -0
- package/src/amazon/session.ts +109 -0
- package/src/autonomy/autonomy-store.ts +5 -5
- package/src/browser-extension-relay/client.ts +124 -0
- package/src/browser-extension-relay/protocol.ts +63 -0
- package/src/browser-extension-relay/server.ts +177 -0
- package/src/bundler/app-bundler.ts +3 -3
- package/src/bundler/bundle-signer.ts +1 -1
- package/src/bundler/signature-verifier.ts +1 -1
- package/src/calls/call-conversation-messages.ts +33 -0
- package/src/calls/call-domain.ts +114 -10
- package/src/calls/call-orchestrator.ts +268 -59
- package/src/calls/call-pointer-messages.ts +53 -0
- package/src/calls/call-recovery.ts +3 -8
- package/src/calls/call-store.ts +69 -87
- package/src/calls/elevenlabs-config.ts +3 -2
- package/src/calls/guardian-action-sweep.ts +105 -0
- package/src/calls/guardian-dispatch.ts +203 -0
- package/src/calls/guardian-question-copy.ts +133 -0
- package/src/calls/relay-server.ts +466 -8
- package/src/calls/speaker-identification.ts +1 -1
- package/src/calls/twilio-config.ts +22 -14
- package/src/calls/twilio-provider.ts +6 -4
- package/src/calls/twilio-rest.ts +308 -7
- package/src/calls/twilio-routes.ts +65 -12
- package/src/calls/types.ts +3 -1
- package/src/channels/types.ts +25 -0
- package/src/cli/amazon.ts +815 -0
- package/src/cli/config-commands.ts +2 -2
- package/src/cli/core-commands.ts +4 -3
- package/src/cli/influencer.ts +244 -0
- package/src/cli/map.ts +89 -6
- package/src/cli.ts +1 -1
- package/src/config/agent-schema.ts +171 -0
- package/src/config/bundled-skills/amazon/SKILL.md +127 -0
- package/src/config/bundled-skills/amazon/icon.svg +13 -0
- package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
- package/src/config/bundled-skills/browser/SKILL.md +1 -0
- package/src/config/bundled-skills/browser/TOOLS.json +17 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
- package/src/config/bundled-skills/doordash/SKILL.md +51 -51
- package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
- package/src/config/bundled-skills/influencer/SKILL.md +144 -0
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
- package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
- package/src/config/bundled-skills/messaging/SKILL.md +33 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/bundled-skills/twitter/icon.svg +14 -0
- package/src/config/bundled-tool-registry.ts +310 -0
- package/src/config/calls-schema.ts +181 -0
- package/src/config/core-schema.ts +309 -0
- package/src/config/defaults.ts +28 -3
- package/src/config/env-registry.ts +162 -0
- package/src/config/env.ts +175 -0
- package/src/config/loader.ts +6 -6
- package/src/config/memory-schema.ts +528 -0
- package/src/config/sandbox-schema.ts +55 -0
- package/src/config/schema.ts +158 -1133
- package/src/config/skill-state.ts +1 -1
- package/src/config/skills-schema.ts +32 -0
- package/src/config/skills.ts +35 -24
- package/src/config/system-prompt.ts +131 -56
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/user-reference.ts +4 -9
- package/src/config/vellum-skills/catalog.json +6 -7
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
- package/src/context/window-manager.ts +27 -7
- package/src/daemon/approval-generators.ts +186 -0
- package/src/daemon/approved-devices-store.ts +140 -0
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/classifier.ts +35 -32
- package/src/daemon/config-watcher.ts +1 -1
- package/src/daemon/daemon-control.ts +217 -0
- package/src/daemon/handlers/apps.ts +2 -3
- package/src/daemon/handlers/config-channels.ts +158 -0
- package/src/daemon/handlers/config-inbox.ts +540 -0
- package/src/daemon/handlers/config-ingress.ts +231 -0
- package/src/daemon/handlers/config-integrations.ts +258 -0
- package/src/daemon/handlers/config-model.ts +143 -0
- package/src/daemon/handlers/config-parental.ts +163 -0
- package/src/daemon/handlers/config-scheduling.ts +172 -0
- package/src/daemon/handlers/config-slack.ts +92 -0
- package/src/daemon/handlers/config-telegram.ts +301 -0
- package/src/daemon/handlers/config-tools.ts +177 -0
- package/src/daemon/handlers/config-trust.ts +104 -0
- package/src/daemon/handlers/config-twilio.ts +1080 -0
- package/src/daemon/handlers/config.ts +53 -1689
- package/src/daemon/handlers/diagnostics.ts +1 -1
- package/src/daemon/handlers/dictation.ts +180 -0
- package/src/daemon/handlers/documents.ts +18 -32
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +11 -0
- package/src/daemon/handlers/misc.ts +3 -5
- package/src/daemon/handlers/pairing.ts +98 -0
- package/src/daemon/handlers/sessions.ts +56 -5
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/handlers/twitter-auth.ts +2 -0
- package/src/daemon/handlers/work-items.ts +17 -9
- package/src/daemon/handlers/workspace-files.ts +4 -3
- package/src/daemon/install-cli-launchers.ts +113 -0
- package/src/daemon/ipc-contract/apps.ts +356 -0
- package/src/daemon/ipc-contract/browser.ts +74 -0
- package/src/daemon/ipc-contract/computer-use.ts +151 -0
- package/src/daemon/ipc-contract/diagnostics.ts +56 -0
- package/src/daemon/ipc-contract/documents.ts +74 -0
- package/src/daemon/ipc-contract/inbox.ts +209 -0
- package/src/daemon/ipc-contract/integrations.ts +284 -0
- package/src/daemon/ipc-contract/memory.ts +48 -0
- package/src/daemon/ipc-contract/messages.ts +211 -0
- package/src/daemon/ipc-contract/pairing.ts +45 -0
- package/src/daemon/ipc-contract/parental-control.ts +95 -0
- package/src/daemon/ipc-contract/schedules.ts +97 -0
- package/src/daemon/ipc-contract/sessions.ts +315 -0
- package/src/daemon/ipc-contract/shared.ts +42 -0
- package/src/daemon/ipc-contract/skills.ts +120 -0
- package/src/daemon/ipc-contract/subagents.ts +58 -0
- package/src/daemon/ipc-contract/surfaces.ts +250 -0
- package/src/daemon/ipc-contract/trust.ts +60 -0
- package/src/daemon/ipc-contract/work-items.ts +225 -0
- package/src/daemon/ipc-contract/workspace.ts +113 -0
- package/src/daemon/ipc-contract-inventory.json +70 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +229 -2426
- package/src/daemon/ipc-protocol.ts +1 -1
- package/src/daemon/ipc-validate.ts +7 -0
- package/src/daemon/lifecycle.ts +97 -377
- package/src/daemon/pairing-store.ts +177 -0
- package/src/daemon/providers-setup.ts +43 -0
- package/src/daemon/ride-shotgun-handler.ts +68 -3
- package/src/daemon/server.ts +66 -46
- package/src/daemon/session-agent-loop-handlers.ts +421 -0
- package/src/daemon/session-agent-loop.ts +117 -275
- package/src/daemon/session-dynamic-profile.ts +1 -1
- package/src/daemon/session-history.ts +1 -1
- package/src/daemon/session-media-retry.ts +1 -1
- package/src/daemon/session-messaging.ts +37 -2
- package/src/daemon/session-notifiers.ts +5 -25
- package/src/daemon/session-process.ts +99 -59
- package/src/daemon/session-queue-manager.ts +96 -4
- package/src/daemon/session-runtime-assembly.ts +199 -10
- package/src/daemon/session-surfaces.ts +19 -4
- package/src/daemon/session-tool-setup.ts +30 -30
- package/src/daemon/session-workspace.ts +1 -1
- package/src/daemon/session.ts +35 -2
- package/src/daemon/shutdown-handlers.ts +122 -0
- package/src/daemon/trace-emitter.ts +1 -1
- package/src/daemon/watch-handler.ts +36 -33
- package/src/doordash/cart-queries.ts +787 -0
- package/src/doordash/client.ts +144 -127
- package/src/doordash/order-queries.ts +85 -0
- package/src/doordash/queries.ts +10 -1308
- package/src/doordash/search-queries.ts +203 -0
- package/src/doordash/session.ts +3 -2
- package/src/doordash/store-queries.ts +246 -0
- package/src/doordash/types.ts +367 -0
- package/src/email/providers/agentmail.ts +2 -1
- package/src/email/providers/index.ts +3 -2
- package/src/email/service.ts +3 -2
- package/src/errors.ts +43 -0
- package/src/home-base/prebuilt/seed.ts +1 -1
- package/src/hooks/cli.ts +6 -5
- package/src/hooks/config.ts +6 -8
- package/src/hooks/discovery.ts +6 -5
- package/src/hooks/manager.ts +4 -3
- package/src/hooks/runner.ts +2 -2
- package/src/hooks/templates.ts +5 -5
- package/src/inbound/public-ingress-urls.ts +6 -4
- package/src/index.ts +4 -2
- package/src/influencer/client.ts +1104 -0
- package/src/instrument.ts +4 -3
- package/src/logfire.ts +4 -3
- package/src/memory/admin.ts +25 -35
- package/src/memory/attachments-store.ts +4 -7
- package/src/memory/channel-delivery-store.ts +30 -1
- package/src/memory/channel-guardian-store.ts +202 -2
- package/src/memory/clarification-resolver.ts +37 -33
- package/src/memory/conflict-store.ts +67 -61
- package/src/memory/contradiction-checker.ts +141 -117
- package/src/memory/conversation-store.ts +335 -51
- package/src/memory/db-connection.ts +27 -4
- package/src/memory/db-init.ts +265 -4
- package/src/memory/db.ts +14 -1
- package/src/memory/embedding-backend.ts +27 -5
- package/src/memory/embedding-ollama.ts +2 -1
- package/src/memory/entity-extractor.ts +38 -35
- package/src/memory/guardian-action-store.ts +430 -0
- package/src/memory/inbox-escalation-projection.ts +59 -0
- package/src/memory/inbox-thread-store.ts +218 -0
- package/src/memory/ingress-invite-store.ts +338 -0
- package/src/memory/ingress-member-store.ts +350 -0
- package/src/memory/items-extractor.ts +91 -97
- package/src/memory/job-handlers/index-maintenance.ts +3 -3
- package/src/memory/job-handlers/media-processing.ts +69 -0
- package/src/memory/job-handlers/summarization.ts +32 -26
- package/src/memory/job-utils.ts +3 -10
- package/src/memory/jobs-store.ts +8 -10
- package/src/memory/jobs-worker.ts +55 -36
- package/src/memory/media-store.ts +759 -0
- package/src/memory/migrations/001-job-deferrals.ts +45 -0
- package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
- package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
- package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
- package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
- package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
- package/src/memory/migrations/index.ts +24 -0
- package/src/memory/migrations/registry.ts +79 -0
- package/src/memory/migrations/validate-migration-state.ts +69 -0
- package/src/memory/qdrant-manager.ts +49 -8
- package/src/memory/query-builder.ts +1 -1
- package/src/memory/raw-query.ts +119 -0
- package/src/memory/recall-cache.ts +4 -1
- package/src/memory/retriever.ts +165 -47
- package/src/memory/schema-migration.ts +25 -984
- package/src/memory/schema.ts +228 -7
- package/src/memory/search/entity.ts +205 -31
- package/src/memory/search/lexical.ts +81 -52
- package/src/memory/search/ranking.ts +27 -23
- package/src/memory/search/semantic.ts +157 -19
- package/src/memory/search/types.ts +24 -0
- package/src/memory/shared-app-links-store.ts +4 -5
- package/src/memory/validation.ts +19 -0
- package/src/messaging/draft-store.ts +5 -6
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +201 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
- package/src/messaging/providers/whatsapp/adapter.ts +136 -0
- package/src/messaging/providers/whatsapp/client.ts +67 -0
- package/src/messaging/style-analyzer.ts +5 -4
- package/src/messaging/thread-summarizer.ts +61 -69
- package/src/messaging/triage-engine.ts +62 -71
- package/src/migrations/config-merge.ts +53 -0
- package/src/migrations/data-layout.ts +68 -0
- package/src/migrations/data-merge.ts +33 -0
- package/src/migrations/hooks-merge.ts +90 -0
- package/src/migrations/index.ts +6 -0
- package/src/migrations/log.ts +23 -0
- package/src/migrations/skills-merge.ts +33 -0
- package/src/migrations/workspace-layout.ts +79 -0
- package/src/permissions/checker.ts +133 -11
- package/src/permissions/prompter.ts +14 -0
- package/src/permissions/shell-identity.ts +31 -1
- package/src/permissions/trust-store.ts +21 -1
- package/src/providers/anthropic/client.ts +4 -4
- package/src/providers/failover.ts +2 -2
- package/src/providers/model-intents.ts +70 -0
- package/src/providers/ollama/client.ts +2 -1
- package/src/providers/provider-send-message.ts +176 -0
- package/src/providers/registry.ts +71 -30
- package/src/providers/retry.ts +35 -1
- package/src/providers/types.ts +12 -1
- package/src/runtime/approval-conversation-turn.ts +97 -0
- package/src/runtime/approval-message-composer.ts +253 -0
- package/src/runtime/channel-approval-parser.ts +36 -2
- package/src/runtime/channel-approvals.ts +11 -24
- package/src/runtime/channel-guardian-service.ts +88 -21
- package/src/runtime/channel-readiness-service.ts +418 -0
- package/src/runtime/channel-readiness-types.ts +35 -0
- package/src/runtime/channel-retry-sweep.ts +184 -0
- package/src/runtime/guardian-context-resolver.ts +108 -0
- package/src/runtime/http-server.ts +275 -717
- package/src/runtime/http-types.ts +59 -3
- package/src/runtime/middleware/auth.ts +116 -0
- package/src/runtime/middleware/error-handler.ts +33 -0
- package/src/runtime/middleware/twilio-validation.ts +127 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +51 -7
- package/src/runtime/routes/channel-delivery-routes.ts +170 -0
- package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
- package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
- package/src/runtime/routes/channel-route-shared.ts +144 -0
- package/src/runtime/routes/channel-routes.ts +32 -1588
- package/src/runtime/routes/conversation-routes.ts +50 -7
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +126 -0
- package/src/runtime/routes/pairing-routes.ts +143 -0
- package/src/runtime/routes/run-routes.ts +15 -1
- package/src/runtime/run-orchestrator.ts +86 -35
- package/src/schedule/schedule-store.ts +36 -32
- package/src/schedule/scheduler.ts +3 -3
- package/src/security/encrypted-store.ts +5 -7
- package/src/security/oauth2.ts +45 -15
- package/src/security/parental-control-store.ts +183 -0
- package/src/security/secret-allowlist.ts +4 -3
- package/src/security/secret-scanner.ts +5 -5
- package/src/security/secure-keys.ts +1 -1
- package/src/security/token-manager.ts +3 -2
- package/src/services/vercel-deploy.ts +6 -2
- package/src/skills/tool-manifest.ts +3 -3
- package/src/skills/vellum-catalog-remote.ts +75 -16
- package/src/slack/slack-webhook.ts +2 -1
- package/src/swarm/orchestrator.ts +92 -1
- package/src/swarm/router-planner.ts +6 -9
- package/src/swarm/worker-prompts.ts +9 -12
- package/src/tasks/task-compiler.ts +19 -28
- package/src/tasks/task-runner.ts +1 -1
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/assets/search.ts +15 -14
- package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
- package/src/tools/browser/auto-navigate.ts +1 -0
- package/src/tools/browser/browser-execution.ts +10 -1
- package/src/tools/browser/browser-manager.ts +119 -4
- package/src/tools/browser/network-recorder.ts +5 -0
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/broker.ts +11 -2
- package/src/tools/credentials/metadata-store.ts +18 -14
- package/src/tools/credentials/post-connect-hooks.ts +61 -0
- package/src/tools/credentials/vault.ts +49 -23
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +68 -9
- package/src/tools/host-terminal/cli-discover.ts +1 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
- package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
- package/src/tools/network/script-proxy/server.ts +1 -1
- package/src/tools/network/script-proxy/session-manager.ts +6 -5
- package/src/tools/network/web-fetch.ts +18 -2
- package/src/tools/network/web-search.ts +8 -4
- package/src/tools/reminder/reminder-store.ts +14 -15
- package/src/tools/schedule/create.ts +1 -0
- package/src/tools/schedule/list.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
- package/src/tools/skills/skill-script-runner.ts +24 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/tasks/work-item-enqueue.ts +2 -2
- package/src/tools/terminal/evaluate-typescript.ts +21 -12
- package/src/tools/terminal/parser.ts +50 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/watcher/delete.ts +6 -0
- package/src/tools/weather/service.ts +1 -1
- package/src/twitter/client.ts +190 -24
- package/src/twitter/router.ts +1 -1
- package/src/twitter/session.ts +4 -3
- package/src/util/clipboard.ts +1 -1
- package/src/util/errors.ts +65 -8
- package/src/util/fs.ts +40 -0
- package/src/util/json.ts +10 -0
- package/src/util/log-redact.ts +189 -0
- package/src/util/logger.ts +19 -17
- package/src/util/object.ts +3 -0
- package/src/util/platform.ts +105 -363
- package/src/util/pricing.ts +1 -1
- package/src/util/promise-guard.ts +1 -1
- package/src/util/retry.ts +19 -0
- package/src/util/row-mapper.ts +79 -0
- package/src/util/silently.ts +21 -0
- package/src/watcher/engine.ts +5 -1
- package/src/watcher/provider-types.ts +20 -0
- package/src/watcher/providers/github.ts +156 -0
- package/src/watcher/providers/gmail.ts +1 -0
- package/src/watcher/providers/google-calendar.ts +1 -0
- package/src/watcher/providers/linear.ts +460 -0
- package/src/watcher/providers/slack.ts +1 -0
- package/src/work-items/work-item-runner.ts +1 -1
- package/src/workspace/git-service.ts +1 -1
- package/src/workspace/provider-commit-message-generator.ts +51 -22
- package/src/__tests__/call-bridge.test.ts +0 -517
- package/src/__tests__/session-process-bridge.test.ts +0 -244
- package/src/calls/call-bridge.ts +0 -168
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
import { loadRawConfig, saveRawConfig } from '../../config/loader.js';
|
|
3
|
+
import { getSecureKey } from '../../security/secure-keys.js';
|
|
4
|
+
import { readHttpToken } from '../../util/platform.js';
|
|
5
|
+
import {
|
|
6
|
+
hasTwilioCredentials,
|
|
7
|
+
updatePhoneNumberWebhooks,
|
|
8
|
+
} from '../../calls/twilio-rest.js';
|
|
9
|
+
import {
|
|
10
|
+
getTwilioVoiceWebhookUrl,
|
|
11
|
+
getTwilioStatusCallbackUrl,
|
|
12
|
+
getTwilioSmsWebhookUrl,
|
|
13
|
+
type IngressConfig,
|
|
14
|
+
} from '../../inbound/public-ingress-urls.js';
|
|
15
|
+
import type { IngressConfigRequest } from '../ipc-protocol.js';
|
|
16
|
+
import { log, CONFIG_RELOAD_DEBOUNCE_MS, defineHandlers, type HandlerContext } from './shared.js';
|
|
17
|
+
import {
|
|
18
|
+
getGatewayInternalBaseUrl,
|
|
19
|
+
getIngressPublicBaseUrl,
|
|
20
|
+
setIngressPublicBaseUrl,
|
|
21
|
+
} from '../../config/env.js';
|
|
22
|
+
|
|
23
|
+
// Lazily capture the env-provided INGRESS_PUBLIC_BASE_URL on first access
|
|
24
|
+
// rather than at module load time. The daemon loads ~/.vellum/.env inside
|
|
25
|
+
// runDaemon() (see lifecycle.ts), which runs AFTER static ES module imports
|
|
26
|
+
// resolve. A module-level snapshot would miss dotenv-provided values.
|
|
27
|
+
let _originalIngressEnvCaptured = false;
|
|
28
|
+
let _originalIngressEnv: string | undefined;
|
|
29
|
+
function getOriginalIngressEnv(): string | undefined {
|
|
30
|
+
if (!_originalIngressEnvCaptured) {
|
|
31
|
+
_originalIngressEnv = getIngressPublicBaseUrl();
|
|
32
|
+
_originalIngressEnvCaptured = true;
|
|
33
|
+
}
|
|
34
|
+
return _originalIngressEnv;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function computeGatewayTarget(): string {
|
|
38
|
+
return getGatewayInternalBaseUrl();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Best-effort call to the gateway's internal reconcile endpoint so that
|
|
43
|
+
* Telegram webhook registration is updated immediately when the ingress
|
|
44
|
+
* URL changes, without requiring a gateway restart.
|
|
45
|
+
*/
|
|
46
|
+
export function triggerGatewayReconcile(ingressPublicBaseUrl: string | undefined): void {
|
|
47
|
+
const gatewayBase = computeGatewayTarget();
|
|
48
|
+
const token = readHttpToken();
|
|
49
|
+
if (!token) {
|
|
50
|
+
log.debug('Skipping gateway reconcile trigger: no HTTP bearer token available');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const url = `${gatewayBase}/internal/telegram/reconcile`;
|
|
55
|
+
const body = JSON.stringify({ ingressPublicBaseUrl: ingressPublicBaseUrl ?? '' });
|
|
56
|
+
|
|
57
|
+
fetch(url, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json',
|
|
61
|
+
'Authorization': `Bearer ${token}`,
|
|
62
|
+
},
|
|
63
|
+
body,
|
|
64
|
+
signal: AbortSignal.timeout(5_000),
|
|
65
|
+
}).then((res) => {
|
|
66
|
+
if (res.ok) {
|
|
67
|
+
log.info('Gateway Telegram webhook reconcile triggered successfully');
|
|
68
|
+
} else {
|
|
69
|
+
log.warn({ status: res.status }, 'Gateway Telegram webhook reconcile returned non-OK status');
|
|
70
|
+
}
|
|
71
|
+
}).catch((err) => {
|
|
72
|
+
log.debug({ err }, 'Gateway Telegram webhook reconcile failed (gateway may not be running)');
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Best-effort Twilio webhook sync helper.
|
|
78
|
+
*
|
|
79
|
+
* Computes the voice, status-callback, and SMS webhook URLs from the current
|
|
80
|
+
* ingress config and pushes them to the Twilio IncomingPhoneNumber API.
|
|
81
|
+
*
|
|
82
|
+
* Returns `{ success, warning }`. When the update fails, `success` is false
|
|
83
|
+
* and `warning` contains a human-readable message. Callers should treat
|
|
84
|
+
* failure as non-fatal so that the primary operation (provision, assign,
|
|
85
|
+
* ingress save) still succeeds.
|
|
86
|
+
*/
|
|
87
|
+
export async function syncTwilioWebhooks(
|
|
88
|
+
phoneNumber: string,
|
|
89
|
+
accountSid: string,
|
|
90
|
+
authToken: string,
|
|
91
|
+
ingressConfig: IngressConfig,
|
|
92
|
+
): Promise<{ success: boolean; warning?: string }> {
|
|
93
|
+
try {
|
|
94
|
+
const voiceUrl = getTwilioVoiceWebhookUrl(ingressConfig);
|
|
95
|
+
const statusCallbackUrl = getTwilioStatusCallbackUrl(ingressConfig);
|
|
96
|
+
const smsUrl = getTwilioSmsWebhookUrl(ingressConfig);
|
|
97
|
+
await updatePhoneNumberWebhooks(accountSid, authToken, phoneNumber, {
|
|
98
|
+
voiceUrl,
|
|
99
|
+
statusCallbackUrl,
|
|
100
|
+
smsUrl,
|
|
101
|
+
});
|
|
102
|
+
log.info({ phoneNumber }, 'Twilio webhooks configured successfully');
|
|
103
|
+
return { success: true };
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
106
|
+
log.warn({ err, phoneNumber }, `Webhook configuration skipped: ${message}`);
|
|
107
|
+
return { success: false, warning: `Webhook configuration skipped: ${message}` };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function handleIngressConfig(
|
|
112
|
+
msg: IngressConfigRequest,
|
|
113
|
+
socket: net.Socket,
|
|
114
|
+
ctx: HandlerContext,
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const localGatewayTarget = computeGatewayTarget();
|
|
117
|
+
try {
|
|
118
|
+
if (msg.action === 'get') {
|
|
119
|
+
const raw = loadRawConfig();
|
|
120
|
+
const ingress = (raw?.ingress ?? {}) as Record<string, unknown>;
|
|
121
|
+
const publicBaseUrl = (ingress.publicBaseUrl as string) ?? '';
|
|
122
|
+
// Backward compatibility: if `enabled` was never explicitly set,
|
|
123
|
+
// infer from whether a publicBaseUrl is configured so existing users
|
|
124
|
+
// who predate the toggle aren't silently disabled.
|
|
125
|
+
const enabled = (ingress.enabled as boolean | undefined) ?? (publicBaseUrl ? true : false);
|
|
126
|
+
ctx.send(socket, { type: 'ingress_config_response', enabled, publicBaseUrl, localGatewayTarget, success: true });
|
|
127
|
+
} else if (msg.action === 'set') {
|
|
128
|
+
const value = (msg.publicBaseUrl ?? '').trim().replace(/\/+$/, '');
|
|
129
|
+
// Ensure we capture the original env value before any mutation below
|
|
130
|
+
getOriginalIngressEnv();
|
|
131
|
+
const raw = loadRawConfig();
|
|
132
|
+
|
|
133
|
+
// Update ingress.publicBaseUrl — this is the single source of truth for
|
|
134
|
+
// the canonical public ingress URL. The gateway receives this value via
|
|
135
|
+
// the INGRESS_PUBLIC_BASE_URL env var at spawn time (see hatch.ts).
|
|
136
|
+
// The gateway also validates Twilio signatures against forwarded public
|
|
137
|
+
// URL headers, so local tunnel updates generally apply without restarts.
|
|
138
|
+
const ingress = (raw?.ingress ?? {}) as Record<string, unknown>;
|
|
139
|
+
ingress.publicBaseUrl = value || undefined;
|
|
140
|
+
if (msg.enabled !== undefined) {
|
|
141
|
+
ingress.enabled = msg.enabled;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const wasSuppressed = ctx.suppressConfigReload;
|
|
145
|
+
ctx.setSuppressConfigReload(true);
|
|
146
|
+
try {
|
|
147
|
+
saveRawConfig({ ...raw, ingress });
|
|
148
|
+
} catch (err) {
|
|
149
|
+
ctx.setSuppressConfigReload(wasSuppressed);
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
ctx.debounceTimers.schedule('__suppress_reset__', () => { ctx.setSuppressConfigReload(false); }, CONFIG_RELOAD_DEBOUNCE_MS);
|
|
153
|
+
|
|
154
|
+
// Propagate to the gateway's process environment so it picks up the
|
|
155
|
+
// new URL when it is restarted. For the local-deployment path the
|
|
156
|
+
// gateway runs as a child process that inherited the assistant's env,
|
|
157
|
+
// so updating process.env here ensures the value is visible when the
|
|
158
|
+
// gateway is restarted (e.g. by the self-upgrade skill or a manual
|
|
159
|
+
// `pkill -f gateway`).
|
|
160
|
+
// Only export the URL when ingress is enabled; clearing it when
|
|
161
|
+
// disabled ensures the gateway stops accepting inbound webhooks.
|
|
162
|
+
const isEnabled = (ingress.enabled as boolean | undefined) ?? (value ? true : false);
|
|
163
|
+
if (value && isEnabled) {
|
|
164
|
+
setIngressPublicBaseUrl(value);
|
|
165
|
+
} else if (isEnabled && getOriginalIngressEnv() !== undefined) {
|
|
166
|
+
// Ingress is enabled but the user cleared the URL — fall back to the
|
|
167
|
+
// env var that was present when the process started.
|
|
168
|
+
setIngressPublicBaseUrl(getOriginalIngressEnv()!);
|
|
169
|
+
} else {
|
|
170
|
+
// Ingress is disabled or no URL is configured and no startup env var
|
|
171
|
+
// exists — remove the env var so the gateway stops accepting webhooks.
|
|
172
|
+
setIngressPublicBaseUrl(undefined);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
ctx.send(socket, { type: 'ingress_config_response', enabled: isEnabled, publicBaseUrl: value, localGatewayTarget, success: true });
|
|
176
|
+
|
|
177
|
+
// Trigger immediate Telegram webhook reconcile on the gateway so
|
|
178
|
+
// that changing the ingress URL takes effect without a restart.
|
|
179
|
+
// Called unconditionally so the gateway clears its in-memory URL
|
|
180
|
+
// when ingress is disabled, preventing stale re-registration on
|
|
181
|
+
// credential rotation.
|
|
182
|
+
// Use the effective URL from process.env (which accounts for the
|
|
183
|
+
// fallback branch above) rather than the raw `value` from the UI.
|
|
184
|
+
const effectiveUrl = isEnabled ? getIngressPublicBaseUrl() : undefined;
|
|
185
|
+
triggerGatewayReconcile(effectiveUrl);
|
|
186
|
+
|
|
187
|
+
// Best-effort Twilio webhook reconciliation: when ingress is being
|
|
188
|
+
// enabled/updated and Twilio numbers are assigned with valid credentials,
|
|
189
|
+
// push the new webhook URLs to Twilio so calls and SMS route correctly.
|
|
190
|
+
if (isEnabled && hasTwilioCredentials()) {
|
|
191
|
+
const currentConfig = loadRawConfig();
|
|
192
|
+
const smsConfig = (currentConfig?.sms ?? {}) as Record<string, unknown>;
|
|
193
|
+
const assignedNumbers = new Set<string>();
|
|
194
|
+
const legacyNumber = (smsConfig.phoneNumber as string) ?? '';
|
|
195
|
+
if (legacyNumber) assignedNumbers.add(legacyNumber);
|
|
196
|
+
|
|
197
|
+
const assistantPhoneNumbers = smsConfig.assistantPhoneNumbers;
|
|
198
|
+
if (assistantPhoneNumbers && typeof assistantPhoneNumbers === 'object' && !Array.isArray(assistantPhoneNumbers)) {
|
|
199
|
+
for (const number of Object.values(assistantPhoneNumbers as Record<string, unknown>)) {
|
|
200
|
+
if (typeof number === 'string' && number) {
|
|
201
|
+
assignedNumbers.add(number);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (assignedNumbers.size > 0) {
|
|
207
|
+
const acctSid = getSecureKey('credential:twilio:account_sid')!;
|
|
208
|
+
const acctToken = getSecureKey('credential:twilio:auth_token')!;
|
|
209
|
+
// Fire-and-forget: webhook sync failure must not block the ingress save.
|
|
210
|
+
// Reconcile every assigned number so assistant-scoped mappings do not
|
|
211
|
+
// retain stale Twilio webhook URLs after ingress URL changes.
|
|
212
|
+
for (const assignedNumber of assignedNumbers) {
|
|
213
|
+
syncTwilioWebhooks(assignedNumber, acctSid, acctToken, currentConfig as IngressConfig)
|
|
214
|
+
.catch(() => {
|
|
215
|
+
// Already logged inside syncTwilioWebhooks
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
ctx.send(socket, { type: 'ingress_config_response', enabled: false, publicBaseUrl: '', localGatewayTarget, success: false, error: `Unknown action: ${String((msg as unknown as Record<string, unknown>).action)}` });
|
|
222
|
+
}
|
|
223
|
+
} catch (err) {
|
|
224
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
225
|
+
ctx.send(socket, { type: 'ingress_config_response', enabled: false, publicBaseUrl: '', localGatewayTarget, success: false, error: message });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export const ingressHandlers = defineHandlers({
|
|
230
|
+
ingress_config: handleIngressConfig,
|
|
231
|
+
});
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
import { loadRawConfig, saveRawConfig } from '../../config/loader.js';
|
|
3
|
+
import { getSecureKey, setSecureKey, deleteSecureKey } from '../../security/secure-keys.js';
|
|
4
|
+
import { upsertCredentialMetadata, deleteCredentialMetadata, getCredentialMetadata } from '../../tools/credentials/metadata-store.js';
|
|
5
|
+
import type {
|
|
6
|
+
VercelApiConfigRequest,
|
|
7
|
+
TwitterIntegrationConfigRequest,
|
|
8
|
+
} from '../ipc-protocol.js';
|
|
9
|
+
import { log, defineHandlers, type HandlerContext } from './shared.js';
|
|
10
|
+
|
|
11
|
+
export function handleVercelApiConfig(
|
|
12
|
+
msg: VercelApiConfigRequest,
|
|
13
|
+
socket: net.Socket,
|
|
14
|
+
ctx: HandlerContext,
|
|
15
|
+
): void {
|
|
16
|
+
try {
|
|
17
|
+
if (msg.action === 'get') {
|
|
18
|
+
const existing = getSecureKey('credential:vercel:api_token');
|
|
19
|
+
ctx.send(socket, {
|
|
20
|
+
type: 'vercel_api_config_response',
|
|
21
|
+
hasToken: !!existing,
|
|
22
|
+
success: true,
|
|
23
|
+
});
|
|
24
|
+
} else if (msg.action === 'set') {
|
|
25
|
+
if (!msg.apiToken) {
|
|
26
|
+
ctx.send(socket, {
|
|
27
|
+
type: 'vercel_api_config_response',
|
|
28
|
+
hasToken: false,
|
|
29
|
+
success: false,
|
|
30
|
+
error: 'apiToken is required for set action',
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const stored = setSecureKey('credential:vercel:api_token', msg.apiToken);
|
|
35
|
+
if (!stored) {
|
|
36
|
+
ctx.send(socket, {
|
|
37
|
+
type: 'vercel_api_config_response',
|
|
38
|
+
hasToken: false,
|
|
39
|
+
success: false,
|
|
40
|
+
error: 'Failed to store API token in secure storage',
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
upsertCredentialMetadata('vercel', 'api_token', {
|
|
45
|
+
allowedTools: ['publish_page', 'unpublish_page'],
|
|
46
|
+
});
|
|
47
|
+
ctx.send(socket, {
|
|
48
|
+
type: 'vercel_api_config_response',
|
|
49
|
+
hasToken: true,
|
|
50
|
+
success: true,
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
deleteSecureKey('credential:vercel:api_token');
|
|
54
|
+
deleteCredentialMetadata('vercel', 'api_token');
|
|
55
|
+
ctx.send(socket, {
|
|
56
|
+
type: 'vercel_api_config_response',
|
|
57
|
+
hasToken: false,
|
|
58
|
+
success: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63
|
+
log.error({ err }, 'Failed to handle Vercel API config');
|
|
64
|
+
ctx.send(socket, {
|
|
65
|
+
type: 'vercel_api_config_response',
|
|
66
|
+
hasToken: false,
|
|
67
|
+
success: false,
|
|
68
|
+
error: message,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function handleTwitterIntegrationConfig(
|
|
74
|
+
msg: TwitterIntegrationConfigRequest,
|
|
75
|
+
socket: net.Socket,
|
|
76
|
+
ctx: HandlerContext,
|
|
77
|
+
): void {
|
|
78
|
+
try {
|
|
79
|
+
if (msg.action === 'get') {
|
|
80
|
+
const raw = loadRawConfig();
|
|
81
|
+
const mode = (raw.twitterIntegrationMode as 'local_byo' | 'managed' | undefined) ?? 'local_byo';
|
|
82
|
+
const strategy = (raw.twitterOperationStrategy as 'oauth' | 'browser' | 'auto' | undefined) ?? 'auto';
|
|
83
|
+
const strategyConfigured = Object.prototype.hasOwnProperty.call(raw, 'twitterOperationStrategy');
|
|
84
|
+
const localClientConfigured = !!getSecureKey('credential:integration:twitter:oauth_client_id');
|
|
85
|
+
const connected = !!getSecureKey('credential:integration:twitter:access_token');
|
|
86
|
+
const meta = getCredentialMetadata('integration:twitter', 'access_token');
|
|
87
|
+
ctx.send(socket, {
|
|
88
|
+
type: 'twitter_integration_config_response',
|
|
89
|
+
success: true,
|
|
90
|
+
mode,
|
|
91
|
+
managedAvailable: false,
|
|
92
|
+
localClientConfigured,
|
|
93
|
+
connected,
|
|
94
|
+
accountInfo: meta?.accountInfo ?? undefined,
|
|
95
|
+
strategy,
|
|
96
|
+
strategyConfigured,
|
|
97
|
+
});
|
|
98
|
+
} else if (msg.action === 'get_strategy') {
|
|
99
|
+
const raw = loadRawConfig();
|
|
100
|
+
const strategy = (raw.twitterOperationStrategy as 'oauth' | 'browser' | 'auto' | undefined) ?? 'auto';
|
|
101
|
+
const strategyConfigured = Object.prototype.hasOwnProperty.call(raw, 'twitterOperationStrategy');
|
|
102
|
+
ctx.send(socket, {
|
|
103
|
+
type: 'twitter_integration_config_response',
|
|
104
|
+
success: true,
|
|
105
|
+
managedAvailable: false,
|
|
106
|
+
localClientConfigured: !!getSecureKey('credential:integration:twitter:oauth_client_id'),
|
|
107
|
+
connected: !!getSecureKey('credential:integration:twitter:access_token'),
|
|
108
|
+
strategy,
|
|
109
|
+
strategyConfigured,
|
|
110
|
+
});
|
|
111
|
+
} else if (msg.action === 'set_strategy') {
|
|
112
|
+
const valid = ['oauth', 'browser', 'auto'];
|
|
113
|
+
const value = msg.strategy;
|
|
114
|
+
if (!value || !valid.includes(value)) {
|
|
115
|
+
ctx.send(socket, {
|
|
116
|
+
type: 'twitter_integration_config_response',
|
|
117
|
+
success: false,
|
|
118
|
+
managedAvailable: false,
|
|
119
|
+
localClientConfigured: false,
|
|
120
|
+
connected: false,
|
|
121
|
+
error: `Invalid strategy value: ${String(value)}. Must be one of: ${valid.join(', ')}`,
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const raw = loadRawConfig();
|
|
126
|
+
raw.twitterOperationStrategy = value;
|
|
127
|
+
saveRawConfig(raw);
|
|
128
|
+
ctx.send(socket, {
|
|
129
|
+
type: 'twitter_integration_config_response',
|
|
130
|
+
success: true,
|
|
131
|
+
managedAvailable: false,
|
|
132
|
+
localClientConfigured: !!getSecureKey('credential:integration:twitter:oauth_client_id'),
|
|
133
|
+
connected: !!getSecureKey('credential:integration:twitter:access_token'),
|
|
134
|
+
strategy: value as 'oauth' | 'browser' | 'auto',
|
|
135
|
+
strategyConfigured: true,
|
|
136
|
+
});
|
|
137
|
+
} else if (msg.action === 'set_mode') {
|
|
138
|
+
const raw = loadRawConfig();
|
|
139
|
+
raw.twitterIntegrationMode = msg.mode ?? 'local_byo';
|
|
140
|
+
saveRawConfig(raw);
|
|
141
|
+
ctx.send(socket, {
|
|
142
|
+
type: 'twitter_integration_config_response',
|
|
143
|
+
success: true,
|
|
144
|
+
mode: msg.mode ?? 'local_byo',
|
|
145
|
+
managedAvailable: false,
|
|
146
|
+
localClientConfigured: !!getSecureKey('credential:integration:twitter:oauth_client_id'),
|
|
147
|
+
connected: !!getSecureKey('credential:integration:twitter:access_token'),
|
|
148
|
+
});
|
|
149
|
+
} else if (msg.action === 'set_local_client') {
|
|
150
|
+
if (!msg.clientId) {
|
|
151
|
+
ctx.send(socket, {
|
|
152
|
+
type: 'twitter_integration_config_response',
|
|
153
|
+
success: false,
|
|
154
|
+
managedAvailable: false,
|
|
155
|
+
localClientConfigured: false,
|
|
156
|
+
connected: false,
|
|
157
|
+
error: 'clientId is required for set_local_client action',
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const previousClientId = getSecureKey('credential:integration:twitter:oauth_client_id');
|
|
162
|
+
const storedId = setSecureKey('credential:integration:twitter:oauth_client_id', msg.clientId);
|
|
163
|
+
if (!storedId) {
|
|
164
|
+
ctx.send(socket, {
|
|
165
|
+
type: 'twitter_integration_config_response',
|
|
166
|
+
success: false,
|
|
167
|
+
managedAvailable: false,
|
|
168
|
+
localClientConfigured: false,
|
|
169
|
+
connected: false,
|
|
170
|
+
error: 'Failed to store client ID in secure storage',
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (msg.clientSecret) {
|
|
175
|
+
const storedSecret = setSecureKey('credential:integration:twitter:oauth_client_secret', msg.clientSecret);
|
|
176
|
+
if (!storedSecret) {
|
|
177
|
+
// Roll back the client ID to its previous value to avoid inconsistent OAuth state
|
|
178
|
+
if (previousClientId) {
|
|
179
|
+
setSecureKey('credential:integration:twitter:oauth_client_id', previousClientId);
|
|
180
|
+
} else {
|
|
181
|
+
deleteSecureKey('credential:integration:twitter:oauth_client_id');
|
|
182
|
+
}
|
|
183
|
+
ctx.send(socket, {
|
|
184
|
+
type: 'twitter_integration_config_response',
|
|
185
|
+
success: false,
|
|
186
|
+
managedAvailable: false,
|
|
187
|
+
localClientConfigured: !!previousClientId,
|
|
188
|
+
connected: false,
|
|
189
|
+
error: 'Failed to store client secret in secure storage',
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// Clear any stale secret when updating client without a secret (e.g. switching to PKCE)
|
|
195
|
+
deleteSecureKey('credential:integration:twitter:oauth_client_secret');
|
|
196
|
+
}
|
|
197
|
+
ctx.send(socket, {
|
|
198
|
+
type: 'twitter_integration_config_response',
|
|
199
|
+
success: true,
|
|
200
|
+
managedAvailable: false,
|
|
201
|
+
localClientConfigured: true,
|
|
202
|
+
connected: !!getSecureKey('credential:integration:twitter:access_token'),
|
|
203
|
+
});
|
|
204
|
+
} else if (msg.action === 'clear_local_client') {
|
|
205
|
+
// If connected, disconnect first
|
|
206
|
+
if (getSecureKey('credential:integration:twitter:access_token')) {
|
|
207
|
+
deleteSecureKey('credential:integration:twitter:access_token');
|
|
208
|
+
deleteSecureKey('credential:integration:twitter:refresh_token');
|
|
209
|
+
deleteCredentialMetadata('integration:twitter', 'access_token');
|
|
210
|
+
}
|
|
211
|
+
deleteSecureKey('credential:integration:twitter:oauth_client_id');
|
|
212
|
+
deleteSecureKey('credential:integration:twitter:oauth_client_secret');
|
|
213
|
+
ctx.send(socket, {
|
|
214
|
+
type: 'twitter_integration_config_response',
|
|
215
|
+
success: true,
|
|
216
|
+
managedAvailable: false,
|
|
217
|
+
localClientConfigured: false,
|
|
218
|
+
connected: false,
|
|
219
|
+
});
|
|
220
|
+
} else if (msg.action === 'disconnect') {
|
|
221
|
+
deleteSecureKey('credential:integration:twitter:access_token');
|
|
222
|
+
deleteSecureKey('credential:integration:twitter:refresh_token');
|
|
223
|
+
deleteCredentialMetadata('integration:twitter', 'access_token');
|
|
224
|
+
ctx.send(socket, {
|
|
225
|
+
type: 'twitter_integration_config_response',
|
|
226
|
+
success: true,
|
|
227
|
+
managedAvailable: false,
|
|
228
|
+
localClientConfigured: !!getSecureKey('credential:integration:twitter:oauth_client_id'),
|
|
229
|
+
connected: false,
|
|
230
|
+
});
|
|
231
|
+
} else {
|
|
232
|
+
ctx.send(socket, {
|
|
233
|
+
type: 'twitter_integration_config_response',
|
|
234
|
+
success: false,
|
|
235
|
+
managedAvailable: false,
|
|
236
|
+
localClientConfigured: false,
|
|
237
|
+
connected: false,
|
|
238
|
+
error: `Unknown action: ${String((msg as unknown as Record<string, unknown>).action)}`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
243
|
+
log.error({ err }, 'Failed to handle Twitter integration config');
|
|
244
|
+
ctx.send(socket, {
|
|
245
|
+
type: 'twitter_integration_config_response',
|
|
246
|
+
success: false,
|
|
247
|
+
managedAvailable: false,
|
|
248
|
+
localClientConfigured: false,
|
|
249
|
+
connected: false,
|
|
250
|
+
error: message,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const integrationHandlers = defineHandlers({
|
|
256
|
+
vercel_api_config: handleVercelApiConfig,
|
|
257
|
+
twitter_integration_config: handleTwitterIntegrationConfig,
|
|
258
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
import { getConfig, loadRawConfig, saveRawConfig } from '../../config/loader.js';
|
|
3
|
+
import { initializeProviders } from '../../providers/registry.js';
|
|
4
|
+
import type {
|
|
5
|
+
ModelSetRequest,
|
|
6
|
+
ImageGenModelSetRequest,
|
|
7
|
+
} from '../ipc-protocol.js';
|
|
8
|
+
import { log, CONFIG_RELOAD_DEBOUNCE_MS, defineHandlers, type HandlerContext } from './shared.js';
|
|
9
|
+
import { MODEL_TO_PROVIDER } from '../session-slash.js';
|
|
10
|
+
|
|
11
|
+
export function handleModelGet(socket: net.Socket, ctx: HandlerContext): void {
|
|
12
|
+
const config = getConfig();
|
|
13
|
+
const configured = Object.keys(config.apiKeys).filter((k) => !!config.apiKeys[k]);
|
|
14
|
+
if (!configured.includes('ollama')) configured.push('ollama');
|
|
15
|
+
ctx.send(socket, {
|
|
16
|
+
type: 'model_info',
|
|
17
|
+
model: config.model,
|
|
18
|
+
provider: config.provider,
|
|
19
|
+
configuredProviders: configured,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function handleModelSet(
|
|
24
|
+
msg: ModelSetRequest,
|
|
25
|
+
socket: net.Socket,
|
|
26
|
+
ctx: HandlerContext,
|
|
27
|
+
): void {
|
|
28
|
+
try {
|
|
29
|
+
// If the requested model is already the current model AND the provider
|
|
30
|
+
// is already aligned with what MODEL_TO_PROVIDER expects, skip expensive
|
|
31
|
+
// reinitialization but still send model_info so the client confirms.
|
|
32
|
+
// If the provider has drifted (e.g. manual config edit), fall through
|
|
33
|
+
// so the full reinit path can repair it.
|
|
34
|
+
{
|
|
35
|
+
const current = getConfig();
|
|
36
|
+
const expectedProvider = MODEL_TO_PROVIDER[msg.model];
|
|
37
|
+
const providerAligned = !expectedProvider || current.provider === expectedProvider;
|
|
38
|
+
if (msg.model === current.model && providerAligned) {
|
|
39
|
+
const configured = Object.keys(current.apiKeys).filter((k) => !!current.apiKeys[k]);
|
|
40
|
+
if (!configured.includes('ollama')) configured.push('ollama');
|
|
41
|
+
ctx.send(socket, {
|
|
42
|
+
type: 'model_info',
|
|
43
|
+
model: current.model,
|
|
44
|
+
provider: current.provider,
|
|
45
|
+
configuredProviders: configured,
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate API key before switching
|
|
52
|
+
const provider = MODEL_TO_PROVIDER[msg.model];
|
|
53
|
+
if (provider && provider !== 'ollama') {
|
|
54
|
+
const currentConfig = getConfig();
|
|
55
|
+
if (!currentConfig.apiKeys[provider]) {
|
|
56
|
+
// Send current model_info so the client resyncs its optimistic state
|
|
57
|
+
// (don't use generic 'error' type — it would interrupt in-flight chat)
|
|
58
|
+
const configured = Object.keys(currentConfig.apiKeys).filter((k) => !!currentConfig.apiKeys[k]);
|
|
59
|
+
if (!configured.includes('ollama')) configured.push('ollama');
|
|
60
|
+
ctx.send(socket, { type: 'model_info', model: currentConfig.model, provider: currentConfig.provider, configuredProviders: configured });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Use raw config to avoid persisting env-var API keys to disk
|
|
66
|
+
const raw = loadRawConfig();
|
|
67
|
+
raw.model = msg.model;
|
|
68
|
+
// Infer provider from model ID to keep provider and model in sync
|
|
69
|
+
raw.provider = provider ?? raw.provider;
|
|
70
|
+
|
|
71
|
+
// Suppress the file watcher callback — handleModelSet already does
|
|
72
|
+
// the full reload sequence; a redundant watcher-triggered reload
|
|
73
|
+
// would incorrectly evict sessions created after this method returns.
|
|
74
|
+
const wasSuppressed = ctx.suppressConfigReload;
|
|
75
|
+
ctx.setSuppressConfigReload(true);
|
|
76
|
+
try {
|
|
77
|
+
saveRawConfig(raw);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
ctx.setSuppressConfigReload(wasSuppressed);
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
ctx.debounceTimers.schedule('__suppress_reset__', () => { ctx.setSuppressConfigReload(false); }, CONFIG_RELOAD_DEBOUNCE_MS);
|
|
83
|
+
|
|
84
|
+
// Re-initialize provider with the new model so LLM calls use it
|
|
85
|
+
const config = getConfig();
|
|
86
|
+
initializeProviders(config);
|
|
87
|
+
|
|
88
|
+
// Evict idle sessions immediately; mark busy ones as stale so they
|
|
89
|
+
// get recreated with the new provider once they finish processing.
|
|
90
|
+
for (const [id, session] of ctx.sessions) {
|
|
91
|
+
if (!session.isProcessing()) {
|
|
92
|
+
session.dispose();
|
|
93
|
+
ctx.sessions.delete(id);
|
|
94
|
+
} else {
|
|
95
|
+
session.markStale();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
ctx.updateConfigFingerprint();
|
|
100
|
+
|
|
101
|
+
ctx.send(socket, {
|
|
102
|
+
type: 'model_info',
|
|
103
|
+
model: config.model,
|
|
104
|
+
provider: config.provider,
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
108
|
+
ctx.send(socket, { type: 'error', message: `Failed to set model: ${message}` });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function handleImageGenModelSet(
|
|
113
|
+
msg: ImageGenModelSetRequest,
|
|
114
|
+
_socket: net.Socket,
|
|
115
|
+
ctx: HandlerContext,
|
|
116
|
+
): void {
|
|
117
|
+
try {
|
|
118
|
+
const raw = loadRawConfig();
|
|
119
|
+
raw.imageGenModel = msg.model;
|
|
120
|
+
|
|
121
|
+
const wasSuppressed = ctx.suppressConfigReload;
|
|
122
|
+
ctx.setSuppressConfigReload(true);
|
|
123
|
+
try {
|
|
124
|
+
saveRawConfig(raw);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
ctx.setSuppressConfigReload(wasSuppressed);
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
ctx.debounceTimers.schedule('__suppress_reset__', () => { ctx.setSuppressConfigReload(false); }, CONFIG_RELOAD_DEBOUNCE_MS);
|
|
130
|
+
|
|
131
|
+
ctx.updateConfigFingerprint();
|
|
132
|
+
log.info({ model: msg.model }, 'Image generation model updated');
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
135
|
+
log.error({ err }, `Failed to set image gen model: ${message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const modelHandlers = defineHandlers({
|
|
140
|
+
model_get: (_msg, socket, ctx) => handleModelGet(socket, ctx),
|
|
141
|
+
model_set: handleModelSet,
|
|
142
|
+
image_gen_model_set: handleImageGenModelSet,
|
|
143
|
+
});
|