@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
|
@@ -5,9 +5,18 @@ import { getLogger } from '../../util/logger.js';
|
|
|
5
5
|
import { checkBrowserRuntime } from './runtime-check.js';
|
|
6
6
|
import { authSessionCache } from './auth-cache.js';
|
|
7
7
|
import type { ExtractedCredential } from './network-recording-types.js';
|
|
8
|
+
import { silentlyWithLog } from '../../util/silently.js';
|
|
8
9
|
|
|
9
10
|
const log = getLogger('browser-manager');
|
|
10
11
|
|
|
12
|
+
function getDownloadsDir(): string {
|
|
13
|
+
const dir = join(getDataDir(), 'browser-downloads');
|
|
14
|
+
mkdirSync(dir, { recursive: true });
|
|
15
|
+
return dir;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type DownloadInfo = { path: string; filename: string };
|
|
19
|
+
|
|
11
20
|
type BrowserContext = {
|
|
12
21
|
newPage(): Promise<Page>;
|
|
13
22
|
close(): Promise<void>;
|
|
@@ -51,7 +60,7 @@ export type Page = {
|
|
|
51
60
|
move(x: number, y: number): Promise<void>;
|
|
52
61
|
wheel(deltaX: number, deltaY: number): Promise<void>;
|
|
53
62
|
};
|
|
54
|
-
|
|
63
|
+
on(event: string, handler: (...args: unknown[]) => void): void;
|
|
55
64
|
};
|
|
56
65
|
|
|
57
66
|
type ScreencastFrameMetadata = {
|
|
@@ -108,6 +117,8 @@ class BrowserManager {
|
|
|
108
117
|
private interactiveModeSessions = new Set<string>();
|
|
109
118
|
private handoffResolvers = new Map<string, () => void>();
|
|
110
119
|
private sessionSenders = new Map<string, (msg: { type: string; sessionId: string }) => void>();
|
|
120
|
+
private downloads = new Map<string, DownloadInfo[]>();
|
|
121
|
+
private pendingDownloads = new Map<string, { resolve: (info: DownloadInfo) => void; reject: (err: Error) => void }[]>();
|
|
111
122
|
|
|
112
123
|
get browserMode(): 'headless' | 'cdp' {
|
|
113
124
|
return this._browserMode;
|
|
@@ -193,7 +204,7 @@ class BrowserManager {
|
|
|
193
204
|
this.contextCreating = (async () => {
|
|
194
205
|
// Deterministic test mode: when launch is injected via setLaunchFn,
|
|
195
206
|
// bypass ambient CDP probing/negotiation and use the injected launcher.
|
|
196
|
-
const hasInjectedLaunchFn = launchPersistentContext
|
|
207
|
+
const hasInjectedLaunchFn = launchPersistentContext != null;
|
|
197
208
|
|
|
198
209
|
if (!hasInjectedLaunchFn) {
|
|
199
210
|
// Try to detect or negotiate CDP before falling back to headless.
|
|
@@ -339,6 +350,11 @@ class BrowserManager {
|
|
|
339
350
|
this.cdpSessions.clear();
|
|
340
351
|
this.screencastCallbacks.clear();
|
|
341
352
|
this.snapshotMaps.clear();
|
|
353
|
+
this.downloads.clear();
|
|
354
|
+
for (const pending of this.pendingDownloads.values()) {
|
|
355
|
+
for (const waiter of pending) waiter.reject(new Error('Browser closed'));
|
|
356
|
+
}
|
|
357
|
+
this.pendingDownloads.clear();
|
|
342
358
|
};
|
|
343
359
|
rawCtx.on('close', this.contextCloseHandler);
|
|
344
360
|
}
|
|
@@ -365,6 +381,9 @@ class BrowserManager {
|
|
|
365
381
|
this.pages.set(sessionId, page);
|
|
366
382
|
this.rawPages.set(sessionId, page);
|
|
367
383
|
|
|
384
|
+
// Track downloads for this page
|
|
385
|
+
this.setupDownloadTracking(sessionId, page);
|
|
386
|
+
|
|
368
387
|
// In CDP mode, keep the window minimized unless we're in an active handoff.
|
|
369
388
|
if (this._browserMode === 'cdp' && !this.interactiveModeSessions.has(sessionId)) {
|
|
370
389
|
await this.moveWindowOffscreen();
|
|
@@ -390,6 +409,13 @@ class BrowserManager {
|
|
|
390
409
|
this.pages.delete(sessionId);
|
|
391
410
|
this.rawPages.delete(sessionId);
|
|
392
411
|
this.snapshotMaps.delete(sessionId);
|
|
412
|
+
this.downloads.delete(sessionId);
|
|
413
|
+
// Reject any pending download waiters
|
|
414
|
+
const pending = this.pendingDownloads.get(sessionId);
|
|
415
|
+
if (pending) {
|
|
416
|
+
for (const waiter of pending) waiter.reject(new Error('Session closed'));
|
|
417
|
+
this.pendingDownloads.delete(sessionId);
|
|
418
|
+
}
|
|
393
419
|
log.debug({ sessionId }, 'Session page closed');
|
|
394
420
|
}
|
|
395
421
|
|
|
@@ -415,6 +441,11 @@ class BrowserManager {
|
|
|
415
441
|
this.pages.clear();
|
|
416
442
|
this.rawPages.clear();
|
|
417
443
|
this.snapshotMaps.clear();
|
|
444
|
+
this.downloads.clear();
|
|
445
|
+
for (const pending of this.pendingDownloads.values()) {
|
|
446
|
+
for (const waiter of pending) waiter.reject(new Error('Browser closed'));
|
|
447
|
+
}
|
|
448
|
+
this.pendingDownloads.clear();
|
|
418
449
|
|
|
419
450
|
if (this.context) {
|
|
420
451
|
// Remove the close listener before intentional close to avoid
|
|
@@ -477,7 +508,7 @@ class BrowserManager {
|
|
|
477
508
|
|
|
478
509
|
cdp.on('Page.screencastFrame', (params) => {
|
|
479
510
|
onFrame({ data: params.data as string, metadata: params.metadata as ScreencastFrameMetadata });
|
|
480
|
-
cdp.send('Page.screencastFrameAck', { sessionId: params.sessionId })
|
|
511
|
+
silentlyWithLog(cdp.send('Page.screencastFrameAck', { sessionId: params.sessionId }), 'screencast frame ack');
|
|
481
512
|
});
|
|
482
513
|
|
|
483
514
|
await cdp.send('Page.startScreencast', {
|
|
@@ -709,8 +740,92 @@ class BrowserManager {
|
|
|
709
740
|
}
|
|
710
741
|
}
|
|
711
742
|
|
|
743
|
+
private setupDownloadTracking(sessionId: string, page: Page): void {
|
|
744
|
+
page.on('download', async (download: unknown) => {
|
|
745
|
+
const dl = download as {
|
|
746
|
+
suggestedFilename(): string;
|
|
747
|
+
path(): Promise<string | null>;
|
|
748
|
+
saveAs(path: string): Promise<void>;
|
|
749
|
+
failure(): Promise<string | null>;
|
|
750
|
+
};
|
|
751
|
+
try {
|
|
752
|
+
const filename = dl.suggestedFilename();
|
|
753
|
+
const destPath = join(getDownloadsDir(), `${Date.now()}-${filename}`);
|
|
754
|
+
await dl.saveAs(destPath);
|
|
755
|
+
const info: DownloadInfo = { path: destPath, filename };
|
|
756
|
+
|
|
757
|
+
// Resolve a pending waiter if one exists, otherwise store for later retrieval
|
|
758
|
+
const pending = this.pendingDownloads.get(sessionId);
|
|
759
|
+
if (pending && pending.length > 0) {
|
|
760
|
+
const waiter = pending.shift()!;
|
|
761
|
+
waiter.resolve(info);
|
|
762
|
+
if (pending.length === 0) this.pendingDownloads.delete(sessionId);
|
|
763
|
+
} else {
|
|
764
|
+
const list = this.downloads.get(sessionId) ?? [];
|
|
765
|
+
list.push(info);
|
|
766
|
+
this.downloads.set(sessionId, list);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
log.info({ sessionId, filename, path: destPath }, 'Download completed');
|
|
770
|
+
} catch (err) {
|
|
771
|
+
const failure = await dl.failure();
|
|
772
|
+
log.warn({ err, failure, sessionId }, 'Download failed');
|
|
773
|
+
|
|
774
|
+
// Reject any pending waiters
|
|
775
|
+
const pending = this.pendingDownloads.get(sessionId);
|
|
776
|
+
if (pending && pending.length > 0) {
|
|
777
|
+
const waiter = pending.shift()!;
|
|
778
|
+
waiter.reject(new Error(`Download failed: ${failure ?? String(err)}`));
|
|
779
|
+
if (pending.length === 0) this.pendingDownloads.delete(sessionId);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
getLastDownload(sessionId: string): DownloadInfo | null {
|
|
786
|
+
const list = this.downloads.get(sessionId);
|
|
787
|
+
if (!list || list.length === 0) return null;
|
|
788
|
+
return list[list.length - 1];
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
waitForDownload(sessionId: string, timeoutMs: number = 30_000): Promise<DownloadInfo> {
|
|
792
|
+
// Check if an unconsumed download already completed for this session
|
|
793
|
+
const existing = this.downloads.get(sessionId);
|
|
794
|
+
if (existing && existing.length > 0) {
|
|
795
|
+
const info = existing.pop()!;
|
|
796
|
+
if (existing.length === 0) this.downloads.delete(sessionId);
|
|
797
|
+
return Promise.resolve(info);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return new Promise<DownloadInfo>((resolve, reject) => {
|
|
801
|
+
const timer = setTimeout(() => {
|
|
802
|
+
// Remove this waiter from the pending list
|
|
803
|
+
const pending = this.pendingDownloads.get(sessionId);
|
|
804
|
+
if (pending) {
|
|
805
|
+
const idx = pending.findIndex(w => w.resolve === wrappedResolve);
|
|
806
|
+
if (idx >= 0) pending.splice(idx, 1);
|
|
807
|
+
if (pending.length === 0) this.pendingDownloads.delete(sessionId);
|
|
808
|
+
}
|
|
809
|
+
reject(new Error(`Download timed out after ${timeoutMs}ms`));
|
|
810
|
+
}, timeoutMs);
|
|
811
|
+
|
|
812
|
+
const wrappedResolve = (info: DownloadInfo) => {
|
|
813
|
+
clearTimeout(timer);
|
|
814
|
+
resolve(info);
|
|
815
|
+
};
|
|
816
|
+
const wrappedReject = (err: Error) => {
|
|
817
|
+
clearTimeout(timer);
|
|
818
|
+
reject(err);
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const pending = this.pendingDownloads.get(sessionId) ?? [];
|
|
822
|
+
pending.push({ resolve: wrappedResolve, reject: wrappedReject });
|
|
823
|
+
this.pendingDownloads.set(sessionId, pending);
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
712
827
|
hasContext(): boolean {
|
|
713
|
-
return this.context
|
|
828
|
+
return this.context != null;
|
|
714
829
|
}
|
|
715
830
|
}
|
|
716
831
|
|
|
@@ -106,6 +106,11 @@ export class NetworkRecorder {
|
|
|
106
106
|
/** URL patterns that indicate a successful login (checked via `includes`). */
|
|
107
107
|
loginSignals: string[] = [];
|
|
108
108
|
|
|
109
|
+
/** Number of network entries recorded so far. */
|
|
110
|
+
get entryCount(): number {
|
|
111
|
+
return this.entries.size;
|
|
112
|
+
}
|
|
113
|
+
|
|
109
114
|
constructor(targetDomain?: string) {
|
|
110
115
|
this.targetDomain = targetDomain;
|
|
111
116
|
}
|
|
@@ -54,6 +54,7 @@ class CallStartTool implements Tool {
|
|
|
54
54
|
task: input.task as string,
|
|
55
55
|
context: input.context as string | undefined,
|
|
56
56
|
conversationId: context.conversationId,
|
|
57
|
+
assistantId: context.assistantId,
|
|
57
58
|
callerIdentityMode: input.caller_identity_mode as 'assistant_number' | 'user_number' | undefined,
|
|
58
59
|
});
|
|
59
60
|
|
|
@@ -20,6 +20,9 @@ import { getLogger } from '../../util/logger.js';
|
|
|
20
20
|
|
|
21
21
|
const log = getLogger('credential-broker');
|
|
22
22
|
|
|
23
|
+
/** Tokens expire after 5 minutes to limit the window for using stale/revoked credentials. */
|
|
24
|
+
const TOKEN_TTL_MS = 5 * 60 * 1000;
|
|
25
|
+
|
|
23
26
|
/**
|
|
24
27
|
* Credential broker that issues single-use tokens for policy-checked credential access.
|
|
25
28
|
*
|
|
@@ -101,6 +104,11 @@ export class CredentialBroker {
|
|
|
101
104
|
if (token.consumed) {
|
|
102
105
|
return { success: false, reason: 'Token already consumed' };
|
|
103
106
|
}
|
|
107
|
+
if (Date.now() - token.createdAt > TOKEN_TTL_MS) {
|
|
108
|
+
this.tokens.delete(tokenId);
|
|
109
|
+
log.info({ tokenId }, 'Token expired (TTL exceeded)');
|
|
110
|
+
return { success: false, reason: 'Token expired' };
|
|
111
|
+
}
|
|
104
112
|
|
|
105
113
|
token.consumed = true;
|
|
106
114
|
const storageKey = `credential:${token.service}:${token.field}`;
|
|
@@ -358,11 +366,12 @@ export class CredentialBroker {
|
|
|
358
366
|
};
|
|
359
367
|
}
|
|
360
368
|
|
|
361
|
-
/** Return the number of active (non-consumed, non-revoked) tokens. */
|
|
369
|
+
/** Return the number of active (non-consumed, non-revoked, non-expired) tokens. */
|
|
362
370
|
get activeTokenCount(): number {
|
|
371
|
+
const now = Date.now();
|
|
363
372
|
let count = 0;
|
|
364
373
|
for (const token of this.tokens.values()) {
|
|
365
|
-
if (!token.consumed) count++;
|
|
374
|
+
if (!token.consumed && now - token.createdAt <= TOKEN_TTL_MS) count++;
|
|
366
375
|
}
|
|
367
376
|
return count;
|
|
368
377
|
}
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* in the secure key backend only.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { writeFileSync, renameSync } from 'node:fs';
|
|
10
10
|
import { join, dirname } from 'node:path';
|
|
11
11
|
import { getDataDir } from '../../util/platform.js';
|
|
12
|
+
import { ensureDir, readTextFileSync } from '../../util/fs.js';
|
|
12
13
|
import { randomUUID } from 'node:crypto';
|
|
13
14
|
import type { CredentialInjectionTemplate } from './policy-types.js';
|
|
14
15
|
|
|
@@ -28,6 +29,8 @@ export interface CredentialMetadata {
|
|
|
28
29
|
oauth2ClientId?: string;
|
|
29
30
|
/** OAuth2 client secret — for providers that require it (e.g. Slack). Stored in metadata for autonomous refresh. */
|
|
30
31
|
oauth2ClientSecret?: string;
|
|
32
|
+
/** How the client authenticates at the token endpoint (client_secret_basic or client_secret_post). */
|
|
33
|
+
oauth2TokenEndpointAuthMethod?: string;
|
|
31
34
|
/** Human-friendly name for this credential (e.g. "fal-primary"). */
|
|
32
35
|
alias?: string;
|
|
33
36
|
/** Templates describing how to inject this credential into proxied requests. */
|
|
@@ -71,7 +74,7 @@ function isUnknownVersion(r: LoadResult): r is UnknownVersionResult {
|
|
|
71
74
|
* Filters out corrupted or incomplete entries during migration.
|
|
72
75
|
*/
|
|
73
76
|
function isValidCredentialRecord(record: unknown): record is Record<string, unknown> {
|
|
74
|
-
if (typeof record !== 'object' || record
|
|
77
|
+
if (typeof record !== 'object' || record == null) return false;
|
|
75
78
|
const r = record as Record<string, unknown>;
|
|
76
79
|
return (
|
|
77
80
|
typeof r.credentialId === 'string' &&
|
|
@@ -99,6 +102,7 @@ function migrateRecordV1toV2(record: Record<string, unknown>): CredentialMetadat
|
|
|
99
102
|
oauth2TokenUrl: typeof record.oauth2TokenUrl === 'string' ? record.oauth2TokenUrl : undefined,
|
|
100
103
|
oauth2ClientId: typeof record.oauth2ClientId === 'string' ? record.oauth2ClientId : undefined,
|
|
101
104
|
oauth2ClientSecret: typeof record.oauth2ClientSecret === 'string' ? record.oauth2ClientSecret : undefined,
|
|
105
|
+
oauth2TokenEndpointAuthMethod: typeof record.oauth2TokenEndpointAuthMethod === 'string' ? record.oauth2TokenEndpointAuthMethod : undefined,
|
|
102
106
|
alias: typeof record.alias === 'string' ? record.alias : undefined,
|
|
103
107
|
injectionTemplates: Array.isArray(record.injectionTemplates)
|
|
104
108
|
? (record.injectionTemplates as CredentialInjectionTemplate[])
|
|
@@ -109,14 +113,13 @@ function migrateRecordV1toV2(record: Record<string, unknown>): CredentialMetadat
|
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
function loadFile(): LoadResult {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
116
|
+
const raw = readTextFileSync(getMetadataPath());
|
|
117
|
+
if (raw == null) {
|
|
114
118
|
return { version: CURRENT_VERSION, credentials: [] };
|
|
115
119
|
}
|
|
116
120
|
try {
|
|
117
|
-
const raw = readFileSync(path, 'utf-8');
|
|
118
121
|
const data = JSON.parse(raw);
|
|
119
|
-
if (typeof data !== 'object' || data
|
|
122
|
+
if (typeof data !== 'object' || data == null) {
|
|
120
123
|
return { version: CURRENT_VERSION, credentials: [] };
|
|
121
124
|
}
|
|
122
125
|
const fileVersion = typeof data.version === 'number' ? data.version : 1;
|
|
@@ -146,9 +149,7 @@ function loadFile(): LoadResult {
|
|
|
146
149
|
function saveFile(data: MetadataFile): void {
|
|
147
150
|
const path = getMetadataPath();
|
|
148
151
|
const dir = dirname(path);
|
|
149
|
-
|
|
150
|
-
mkdirSync(dir, { recursive: true });
|
|
151
|
-
}
|
|
152
|
+
ensureDir(dir);
|
|
152
153
|
const tmpPath = join(dir, `.tmp-${randomUUID()}`);
|
|
153
154
|
writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
154
155
|
renameSync(tmpPath, path);
|
|
@@ -186,6 +187,7 @@ export function upsertCredentialMetadata(
|
|
|
186
187
|
oauth2ClientId?: string;
|
|
187
188
|
/** Pass `null` to explicitly clear a previously-set client secret. */
|
|
188
189
|
oauth2ClientSecret?: string | null;
|
|
190
|
+
oauth2TokenEndpointAuthMethod?: string;
|
|
189
191
|
/** Pass `null` to explicitly clear a previously-set alias. */
|
|
190
192
|
alias?: string | null;
|
|
191
193
|
/** Pass `null` to explicitly clear injection templates. */
|
|
@@ -208,7 +210,7 @@ export function upsertCredentialMetadata(
|
|
|
208
210
|
if (policy?.allowedDomains !== undefined) existing.allowedDomains = policy.allowedDomains;
|
|
209
211
|
if (policy?.usageDescription !== undefined) existing.usageDescription = policy.usageDescription;
|
|
210
212
|
if (policy?.expiresAt !== undefined) {
|
|
211
|
-
if (policy.expiresAt
|
|
213
|
+
if (policy.expiresAt == null) {
|
|
212
214
|
delete existing.expiresAt;
|
|
213
215
|
} else {
|
|
214
216
|
existing.expiresAt = policy.expiresAt;
|
|
@@ -216,7 +218,7 @@ export function upsertCredentialMetadata(
|
|
|
216
218
|
}
|
|
217
219
|
if (policy?.grantedScopes !== undefined) existing.grantedScopes = policy.grantedScopes;
|
|
218
220
|
if (policy?.accountInfo !== undefined) {
|
|
219
|
-
if (policy.accountInfo
|
|
221
|
+
if (policy.accountInfo == null) {
|
|
220
222
|
delete existing.accountInfo;
|
|
221
223
|
} else {
|
|
222
224
|
existing.accountInfo = policy.accountInfo;
|
|
@@ -225,21 +227,22 @@ export function upsertCredentialMetadata(
|
|
|
225
227
|
if (policy?.oauth2TokenUrl !== undefined) existing.oauth2TokenUrl = policy.oauth2TokenUrl;
|
|
226
228
|
if (policy?.oauth2ClientId !== undefined) existing.oauth2ClientId = policy.oauth2ClientId;
|
|
227
229
|
if (policy?.oauth2ClientSecret !== undefined) {
|
|
228
|
-
if (policy.oauth2ClientSecret
|
|
230
|
+
if (policy.oauth2ClientSecret == null) {
|
|
229
231
|
delete existing.oauth2ClientSecret;
|
|
230
232
|
} else {
|
|
231
233
|
existing.oauth2ClientSecret = policy.oauth2ClientSecret;
|
|
232
234
|
}
|
|
233
235
|
}
|
|
236
|
+
if (policy?.oauth2TokenEndpointAuthMethod !== undefined) existing.oauth2TokenEndpointAuthMethod = policy.oauth2TokenEndpointAuthMethod;
|
|
234
237
|
if (policy?.alias !== undefined) {
|
|
235
|
-
if (policy.alias
|
|
238
|
+
if (policy.alias == null) {
|
|
236
239
|
delete existing.alias;
|
|
237
240
|
} else {
|
|
238
241
|
existing.alias = policy.alias;
|
|
239
242
|
}
|
|
240
243
|
}
|
|
241
244
|
if (policy?.injectionTemplates !== undefined) {
|
|
242
|
-
if (policy.injectionTemplates
|
|
245
|
+
if (policy.injectionTemplates == null) {
|
|
243
246
|
delete existing.injectionTemplates;
|
|
244
247
|
} else {
|
|
245
248
|
existing.injectionTemplates = policy.injectionTemplates;
|
|
@@ -263,6 +266,7 @@ export function upsertCredentialMetadata(
|
|
|
263
266
|
oauth2TokenUrl: policy?.oauth2TokenUrl,
|
|
264
267
|
oauth2ClientId: policy?.oauth2ClientId,
|
|
265
268
|
oauth2ClientSecret: policy?.oauth2ClientSecret ?? undefined,
|
|
269
|
+
oauth2TokenEndpointAuthMethod: policy?.oauth2TokenEndpointAuthMethod,
|
|
266
270
|
alias: policy?.alias ?? undefined,
|
|
267
271
|
injectionTemplates: policy?.injectionTemplates ?? undefined,
|
|
268
272
|
createdAt: now,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-connect hooks for OAuth2 services.
|
|
3
|
+
*
|
|
4
|
+
* This module decouples provider-specific post-connection side effects
|
|
5
|
+
* (e.g. sending a welcome DM after Slack OAuth) from the generic vault
|
|
6
|
+
* OAuth2 flow. Each hook is keyed by canonical service name and receives
|
|
7
|
+
* the raw token response so it can perform provider-specific actions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { authTest, conversationsOpen, postMessage } from '../../messaging/providers/slack/client.js';
|
|
11
|
+
import { getLogger } from '../../util/logger.js';
|
|
12
|
+
|
|
13
|
+
const log = getLogger('post-connect-hooks');
|
|
14
|
+
|
|
15
|
+
export interface PostConnectHookContext {
|
|
16
|
+
service: string;
|
|
17
|
+
rawTokenResponse: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type PostConnectHook = (ctx: PostConnectHookContext) => Promise<void>;
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Provider-specific hooks
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
async function slackWelcomeDM(ctx: PostConnectHookContext): Promise<void> {
|
|
27
|
+
const botToken = ctx.rawTokenResponse.access_token as string | undefined;
|
|
28
|
+
const authedUser = ctx.rawTokenResponse.authed_user as Record<string, unknown> | undefined;
|
|
29
|
+
const installingUserId = authedUser?.id as string | undefined;
|
|
30
|
+
if (!botToken || !installingUserId) return;
|
|
31
|
+
|
|
32
|
+
const identity = await authTest(botToken);
|
|
33
|
+
const dmChannel = await conversationsOpen(botToken, installingUserId);
|
|
34
|
+
const welcomeMsg =
|
|
35
|
+
`You have installed ${identity.user}, an AI Assistant, on ${identity.team}. ` +
|
|
36
|
+
`You can manage the assistant experience for this workspace by chatting with the assistant or from the Settings page.`;
|
|
37
|
+
await postMessage(botToken, dmChannel.channel.id, welcomeMsg);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Registry
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
const POST_CONNECT_HOOKS: Record<string, PostConnectHook> = {
|
|
45
|
+
'integration:slack': slackWelcomeDM,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Run the post-connect hook for a service, if one is registered.
|
|
50
|
+
* Failures are logged but never propagated -- they must not break the OAuth flow.
|
|
51
|
+
*/
|
|
52
|
+
export async function runPostConnectHook(ctx: PostConnectHookContext): Promise<void> {
|
|
53
|
+
const hook = POST_CONNECT_HOOKS[ctx.service];
|
|
54
|
+
if (!hook) return;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await hook(ctx);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
log.warn({ err, service: ctx.service }, 'Post-connect hook failed (non-fatal)');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -13,8 +13,8 @@ import { upsertCredentialMetadata, deleteCredentialMetadata, getCredentialMetada
|
|
|
13
13
|
import { validatePolicyInput, toPolicyFromInput } from './policy-validate.js';
|
|
14
14
|
import type { CredentialPolicyInput, CredentialInjectionTemplate } from './policy-types.js';
|
|
15
15
|
import { credentialBroker } from './broker.js';
|
|
16
|
-
import { startOAuth2Flow } from '../../security/oauth2.js';
|
|
17
|
-
import {
|
|
16
|
+
import { startOAuth2Flow, type TokenEndpointAuthMethod } from '../../security/oauth2.js';
|
|
17
|
+
import { runPostConnectHook } from './post-connect-hooks.js';
|
|
18
18
|
import { getConfig } from '../../config/loader.js';
|
|
19
19
|
import { getLogger } from '../../util/logger.js';
|
|
20
20
|
|
|
@@ -32,6 +32,10 @@ interface WellKnownOAuthConfig {
|
|
|
32
32
|
scopes: string[];
|
|
33
33
|
userinfoUrl?: string;
|
|
34
34
|
extraParams?: Record<string, string>;
|
|
35
|
+
/** How to send client credentials at the token endpoint. Defaults to client_secret_post. */
|
|
36
|
+
tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
|
|
37
|
+
/** Injection templates auto-applied to the access_token credential after a successful OAuth2 connect. */
|
|
38
|
+
injectionTemplates?: CredentialInjectionTemplate[];
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
const WELL_KNOWN_OAUTH: Record<string, WellKnownOAuthConfig> = {
|
|
@@ -64,12 +68,34 @@ const WELL_KNOWN_OAUTH: Record<string, WellKnownOAuthConfig> = {
|
|
|
64
68
|
user_scope: 'channels:read,channels:history,groups:read,groups:history,im:read,im:history,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write',
|
|
65
69
|
},
|
|
66
70
|
},
|
|
71
|
+
// Notion uses a simple OAuth2 flow with client_secret_basic auth at the token endpoint.
|
|
72
|
+
// The access token is long-lived (no expiry) and scopes are configured per-integration in Notion
|
|
73
|
+
// (the authorization URL accepts owner=user but there are no traditional scope strings to request).
|
|
74
|
+
'integration:notion': {
|
|
75
|
+
authUrl: 'https://api.notion.com/v1/oauth/authorize',
|
|
76
|
+
tokenUrl: 'https://api.notion.com/v1/oauth/token',
|
|
77
|
+
scopes: [],
|
|
78
|
+
extraParams: { owner: 'user' },
|
|
79
|
+
// Notion requires HTTP Basic Auth (base64 of client_id:client_secret) at the token endpoint,
|
|
80
|
+
// not the default client_secret_post form-body approach.
|
|
81
|
+
tokenEndpointAuthMethod: 'client_secret_basic',
|
|
82
|
+
// Auto-inject the Bearer token for all Notion API calls made through the sandbox proxy.
|
|
83
|
+
injectionTemplates: [
|
|
84
|
+
{
|
|
85
|
+
hostPattern: 'api.notion.com',
|
|
86
|
+
injectionType: 'header',
|
|
87
|
+
headerName: 'Authorization',
|
|
88
|
+
valuePrefix: 'Bearer ',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
67
92
|
};
|
|
68
93
|
|
|
69
94
|
/** Map shorthand aliases to canonical service names. */
|
|
70
95
|
const SERVICE_ALIASES: Record<string, string> = {
|
|
71
96
|
gmail: 'integration:gmail',
|
|
72
97
|
slack: 'integration:slack',
|
|
98
|
+
notion: 'integration:notion',
|
|
73
99
|
};
|
|
74
100
|
|
|
75
101
|
/** Resolve a service name through aliases. */
|
|
@@ -198,6 +224,11 @@ class CredentialStoreTool implements Tool {
|
|
|
198
224
|
type: 'string',
|
|
199
225
|
description: 'OAuth2 client secret for providers that require it (e.g. Google, Slack). If omitted, looked up from previously stored credentials; if still absent, PKCE-only is used (only for oauth2_connect action)',
|
|
200
226
|
},
|
|
227
|
+
token_endpoint_auth_method: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
enum: ['client_secret_basic', 'client_secret_post'],
|
|
230
|
+
description: 'How to send client credentials at the token endpoint: "client_secret_post" (default, in POST body) or "client_secret_basic" (HTTP Basic Auth header). Only for oauth2_connect action.',
|
|
231
|
+
},
|
|
201
232
|
alias: {
|
|
202
233
|
type: 'string',
|
|
203
234
|
description: 'Human-friendly name for this credential (only for store action), e.g. "fal-primary"',
|
|
@@ -269,7 +300,7 @@ class CredentialStoreTool implements Tool {
|
|
|
269
300
|
injectionTemplates = [];
|
|
270
301
|
for (let i = 0; i < rawTemplates.length; i++) {
|
|
271
302
|
const t = rawTemplates[i] as Record<string, unknown>;
|
|
272
|
-
if (typeof t !== 'object' || t
|
|
303
|
+
if (typeof t !== 'object' || t == null) {
|
|
273
304
|
templateErrors.push(`injection_templates[${i}] must be an object`);
|
|
274
305
|
continue;
|
|
275
306
|
}
|
|
@@ -540,10 +571,15 @@ class CredentialStoreTool implements Tool {
|
|
|
540
571
|
?? findStoredOAuthField(service, ['client_id', 'oauth_client_id']);
|
|
541
572
|
const clientSecret = (input.client_secret as string | undefined)
|
|
542
573
|
?? findStoredOAuthField(service, ['client_secret', 'oauth_client_secret']);
|
|
574
|
+
const tokenEndpointAuthMethod = (input.token_endpoint_auth_method as TokenEndpointAuthMethod | undefined)
|
|
575
|
+
?? wellKnown?.tokenEndpointAuthMethod;
|
|
543
576
|
|
|
544
577
|
if (!authUrl) return { content: 'Error: auth_url is required for oauth2_connect action (no well-known config for this service)', isError: true };
|
|
545
578
|
if (!tokenUrl) return { content: 'Error: token_url is required for oauth2_connect action (no well-known config for this service)', isError: true };
|
|
546
|
-
|
|
579
|
+
// Scopes are optional — some providers (e.g. Notion) configure authorization at the integration
|
|
580
|
+
// level and don't use traditional scope strings. Reject only when scopes is entirely absent (not
|
|
581
|
+
// provided and no well-known config), not when it is an empty array.
|
|
582
|
+
if (!scopes) return { content: 'Error: scopes is required for oauth2_connect action (no well-known config for this service)', isError: true };
|
|
547
583
|
if (!clientId) return { content: 'Error: client_id is required for oauth2_connect action. Provide it directly or store it first with credential_store.', isError: true };
|
|
548
584
|
|
|
549
585
|
if (!context.isInteractive) {
|
|
@@ -560,7 +596,7 @@ class CredentialStoreTool implements Tool {
|
|
|
560
596
|
const allowedTools = input.allowed_tools as string[] | undefined;
|
|
561
597
|
|
|
562
598
|
const { tokens, grantedScopes, rawTokenResponse } = await startOAuth2Flow(
|
|
563
|
-
{ authUrl, tokenUrl, scopes, clientId, clientSecret, extraParams, userinfoUrl },
|
|
599
|
+
{ authUrl, tokenUrl, scopes, clientId, clientSecret, extraParams, userinfoUrl, tokenEndpointAuthMethod },
|
|
564
600
|
{
|
|
565
601
|
openUrl: (url) => {
|
|
566
602
|
context.sendToClient?.({ type: 'open_url', url, title: `Connect ${service}` });
|
|
@@ -602,6 +638,10 @@ class CredentialStoreTool implements Tool {
|
|
|
602
638
|
}
|
|
603
639
|
}
|
|
604
640
|
|
|
641
|
+
// Well-known configs may supply injection templates (e.g. auto-inject Bearer header for
|
|
642
|
+
// api.notion.com) so that bash/network_mode=proxied works without manual setup.
|
|
643
|
+
const wellKnownInjectionTemplates = wellKnown?.injectionTemplates;
|
|
644
|
+
|
|
605
645
|
upsertCredentialMetadata(service, 'access_token', {
|
|
606
646
|
allowedTools: allowedTools ?? [],
|
|
607
647
|
expiresAt,
|
|
@@ -610,6 +650,8 @@ class CredentialStoreTool implements Tool {
|
|
|
610
650
|
oauth2TokenUrl: tokenUrl,
|
|
611
651
|
oauth2ClientId: clientId,
|
|
612
652
|
...(clientSecret ? { oauth2ClientSecret: clientSecret } : {}),
|
|
653
|
+
...(tokenEndpointAuthMethod ? { oauth2TokenEndpointAuthMethod: tokenEndpointAuthMethod } : {}),
|
|
654
|
+
...(wellKnownInjectionTemplates ? { injectionTemplates: wellKnownInjectionTemplates } : {}),
|
|
613
655
|
});
|
|
614
656
|
|
|
615
657
|
if (tokens.refreshToken) {
|
|
@@ -619,24 +661,8 @@ class CredentialStoreTool implements Tool {
|
|
|
619
661
|
}
|
|
620
662
|
}
|
|
621
663
|
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
try {
|
|
625
|
-
const botToken = rawTokenResponse.access_token as string | undefined;
|
|
626
|
-
const authedUser = rawTokenResponse.authed_user as Record<string, unknown> | undefined;
|
|
627
|
-
const installingUserId = authedUser?.id as string | undefined;
|
|
628
|
-
if (botToken && installingUserId) {
|
|
629
|
-
const identity = await authTest(botToken);
|
|
630
|
-
const dmChannel = await conversationsOpen(botToken, installingUserId);
|
|
631
|
-
const welcomeMsg =
|
|
632
|
-
`You have installed ${identity.user}, an AI Assistant, on ${identity.team}. ` +
|
|
633
|
-
`Manage the assistant experience for this workspace in the workspace settings page.`;
|
|
634
|
-
await postMessage(botToken, dmChannel.channel.id, welcomeMsg);
|
|
635
|
-
}
|
|
636
|
-
} catch (err) {
|
|
637
|
-
log.warn({ err }, 'Failed to send Slack welcome DM (non-fatal)');
|
|
638
|
-
}
|
|
639
|
-
}
|
|
664
|
+
// Run any provider-specific post-connect actions (e.g. Slack welcome DM)
|
|
665
|
+
await runPostConnectHook({ service, rawTokenResponse });
|
|
640
666
|
|
|
641
667
|
return {
|
|
642
668
|
content: `Successfully connected "${service}"${accountInfo ? ` as ${accountInfo}` : ''}. The service is now ready to use.`,
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ExecutionTarget } from './types.js';
|
|
2
2
|
import { getTool } from './registry.js';
|
|
3
3
|
|
|
4
|
-
export
|
|
4
|
+
export interface ManifestOverride {
|
|
5
|
+
risk: 'low' | 'medium' | 'high';
|
|
6
|
+
execution_target: 'host' | 'sandbox';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resolveExecutionTarget(toolName: string, manifestOverride?: ManifestOverride): ExecutionTarget {
|
|
5
10
|
const tool = getTool(toolName);
|
|
6
11
|
// Manifest-declared execution target is authoritative — check it first so
|
|
7
12
|
// skill tools with host_/computer_use_ prefixes aren't mis-classified.
|
|
@@ -13,6 +18,11 @@ export function resolveExecutionTarget(toolName: string): ExecutionTarget {
|
|
|
13
18
|
if (tool?.executionMode === 'proxy') {
|
|
14
19
|
return 'host';
|
|
15
20
|
}
|
|
21
|
+
// Use manifest metadata for unregistered skill tools so the Permission
|
|
22
|
+
// Simulator shows accurate execution targets instead of defaulting to sandbox.
|
|
23
|
+
if (!tool && manifestOverride) {
|
|
24
|
+
return manifestOverride.execution_target;
|
|
25
|
+
}
|
|
16
26
|
// Prefix heuristics for core tools that don't declare an explicit target.
|
|
17
27
|
if (toolName.startsWith('host_') || toolName.startsWith('computer_use_')) {
|
|
18
28
|
return 'host';
|