@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,222 @@
|
|
|
1
|
+
import { describe, test, expect, mock } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
let mockConfig: Record<string, unknown> = {};
|
|
4
|
+
|
|
5
|
+
mock.module('../config/loader.js', () => ({
|
|
6
|
+
loadConfig: () => mockConfig,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { buildElevenLabsVoiceSpec, resolveVoiceQualityProfile, isVoiceProfileValid } from '../calls/voice-quality.js';
|
|
10
|
+
|
|
11
|
+
describe('buildElevenLabsVoiceSpec', () => {
|
|
12
|
+
test('returns bare voiceId when no model is set', () => {
|
|
13
|
+
expect(buildElevenLabsVoiceSpec({ voiceId: 'abc123' })).toBe('abc123');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('returns empty string when voiceId is empty', () => {
|
|
17
|
+
expect(buildElevenLabsVoiceSpec({ voiceId: '' })).toBe('');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('returns empty string when voiceId is whitespace', () => {
|
|
21
|
+
expect(buildElevenLabsVoiceSpec({ voiceId: ' ' })).toBe('');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('returns bare voiceId when voiceModelId is empty', () => {
|
|
25
|
+
expect(buildElevenLabsVoiceSpec({ voiceId: 'abc123', voiceModelId: '' })).toBe('abc123');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('returns bare voiceId when voiceModelId is whitespace', () => {
|
|
29
|
+
expect(buildElevenLabsVoiceSpec({ voiceId: 'abc123', voiceModelId: ' ' })).toBe('abc123');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('appends model and defaults when voiceModelId is provided', () => {
|
|
33
|
+
const result = buildElevenLabsVoiceSpec({ voiceId: 'abc123', voiceModelId: 'eleven_turbo_v2' });
|
|
34
|
+
expect(result).toBe('abc123-eleven_turbo_v2-1_0.5_0.75');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('uses custom speed, stability, and similarity values', () => {
|
|
38
|
+
const result = buildElevenLabsVoiceSpec({
|
|
39
|
+
voiceId: 'voice1',
|
|
40
|
+
voiceModelId: 'model1',
|
|
41
|
+
speed: 1.5,
|
|
42
|
+
stability: 0.8,
|
|
43
|
+
similarityBoost: 0.9,
|
|
44
|
+
});
|
|
45
|
+
expect(result).toBe('voice1-model1-1.5_0.8_0.9');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('trims whitespace from voiceId', () => {
|
|
49
|
+
expect(buildElevenLabsVoiceSpec({ voiceId: ' abc123 ' })).toBe('abc123');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('resolveVoiceQualityProfile', () => {
|
|
54
|
+
test('returns standard profile for twilio_standard mode', () => {
|
|
55
|
+
mockConfig = {
|
|
56
|
+
calls: {
|
|
57
|
+
voice: {
|
|
58
|
+
mode: 'twilio_standard',
|
|
59
|
+
language: 'en-US',
|
|
60
|
+
transcriptionProvider: 'Google',
|
|
61
|
+
fallbackToStandardOnError: false,
|
|
62
|
+
elevenlabs: {},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const profile = resolveVoiceQualityProfile();
|
|
67
|
+
expect(profile.mode).toBe('twilio_standard');
|
|
68
|
+
expect(profile.ttsProvider).toBe('Google');
|
|
69
|
+
expect(profile.voice).toBe('Google.en-US-Journey-O');
|
|
70
|
+
expect(profile.validationErrors).toHaveLength(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('returns elevenlabs profile for twilio_elevenlabs_tts mode', () => {
|
|
74
|
+
mockConfig = {
|
|
75
|
+
calls: {
|
|
76
|
+
voice: {
|
|
77
|
+
mode: 'twilio_elevenlabs_tts',
|
|
78
|
+
language: 'en-US',
|
|
79
|
+
transcriptionProvider: 'Google',
|
|
80
|
+
fallbackToStandardOnError: false,
|
|
81
|
+
elevenlabs: { voiceId: 'elvoice1' },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const profile = resolveVoiceQualityProfile();
|
|
86
|
+
expect(profile.mode).toBe('twilio_elevenlabs_tts');
|
|
87
|
+
expect(profile.ttsProvider).toBe('ElevenLabs');
|
|
88
|
+
expect(profile.voice).toBe('elvoice1');
|
|
89
|
+
expect(profile.validationErrors).toHaveLength(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('falls back to standard when voiceId missing and fallback enabled', () => {
|
|
93
|
+
mockConfig = {
|
|
94
|
+
calls: {
|
|
95
|
+
voice: {
|
|
96
|
+
mode: 'twilio_elevenlabs_tts',
|
|
97
|
+
language: 'en-US',
|
|
98
|
+
transcriptionProvider: 'Google',
|
|
99
|
+
fallbackToStandardOnError: true,
|
|
100
|
+
elevenlabs: { voiceId: '' },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const profile = resolveVoiceQualityProfile();
|
|
105
|
+
expect(profile.mode).toBe('twilio_standard');
|
|
106
|
+
expect(profile.validationErrors.length).toBeGreaterThan(0);
|
|
107
|
+
expect(profile.validationErrors[0]).toContain('falling back');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('returns validation error when voiceId missing and fallback disabled', () => {
|
|
111
|
+
mockConfig = {
|
|
112
|
+
calls: {
|
|
113
|
+
voice: {
|
|
114
|
+
mode: 'twilio_elevenlabs_tts',
|
|
115
|
+
language: 'en-US',
|
|
116
|
+
transcriptionProvider: 'Google',
|
|
117
|
+
fallbackToStandardOnError: false,
|
|
118
|
+
elevenlabs: { voiceId: '' },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
const profile = resolveVoiceQualityProfile();
|
|
123
|
+
expect(profile.mode).toBe('twilio_elevenlabs_tts');
|
|
124
|
+
expect(profile.validationErrors.length).toBeGreaterThan(0);
|
|
125
|
+
expect(profile.validationErrors[0]).toContain('voiceId is required');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('returns elevenlabs_agent profile with agentId', () => {
|
|
129
|
+
mockConfig = {
|
|
130
|
+
calls: {
|
|
131
|
+
voice: {
|
|
132
|
+
mode: 'elevenlabs_agent',
|
|
133
|
+
language: 'en-US',
|
|
134
|
+
transcriptionProvider: 'Google',
|
|
135
|
+
fallbackToStandardOnError: false,
|
|
136
|
+
elevenlabs: { voiceId: 'voice1', agentId: 'agent123' },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
const profile = resolveVoiceQualityProfile();
|
|
141
|
+
expect(profile.mode).toBe('elevenlabs_agent');
|
|
142
|
+
expect(profile.agentId).toBe('agent123');
|
|
143
|
+
expect(profile.validationErrors).toHaveLength(0);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('falls back to standard when agentId missing and fallback enabled', () => {
|
|
147
|
+
mockConfig = {
|
|
148
|
+
calls: {
|
|
149
|
+
voice: {
|
|
150
|
+
mode: 'elevenlabs_agent',
|
|
151
|
+
language: 'en-US',
|
|
152
|
+
transcriptionProvider: 'Google',
|
|
153
|
+
fallbackToStandardOnError: true,
|
|
154
|
+
elevenlabs: { voiceId: 'voice1', agentId: '' },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const profile = resolveVoiceQualityProfile();
|
|
159
|
+
expect(profile.mode).toBe('twilio_standard');
|
|
160
|
+
expect(profile.validationErrors[0]).toContain('falling back');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('returns validation error when agentId missing and fallback disabled', () => {
|
|
164
|
+
mockConfig = {
|
|
165
|
+
calls: {
|
|
166
|
+
voice: {
|
|
167
|
+
mode: 'elevenlabs_agent',
|
|
168
|
+
language: 'en-US',
|
|
169
|
+
transcriptionProvider: 'Google',
|
|
170
|
+
fallbackToStandardOnError: false,
|
|
171
|
+
elevenlabs: { voiceId: 'voice1', agentId: '' },
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
const profile = resolveVoiceQualityProfile();
|
|
176
|
+
expect(profile.mode).toBe('elevenlabs_agent');
|
|
177
|
+
expect(profile.validationErrors.length).toBeGreaterThan(0);
|
|
178
|
+
expect(profile.validationErrors[0]).toContain('agentId is required');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('returns standard profile for unknown mode', () => {
|
|
182
|
+
mockConfig = {
|
|
183
|
+
calls: {
|
|
184
|
+
voice: {
|
|
185
|
+
mode: 'unknown_mode',
|
|
186
|
+
language: 'en-US',
|
|
187
|
+
transcriptionProvider: 'Google',
|
|
188
|
+
fallbackToStandardOnError: false,
|
|
189
|
+
elevenlabs: {},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
const profile = resolveVoiceQualityProfile();
|
|
194
|
+
expect(profile.mode).toBe('twilio_standard');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('isVoiceProfileValid', () => {
|
|
199
|
+
test('returns true for profile with no errors', () => {
|
|
200
|
+
expect(isVoiceProfileValid({
|
|
201
|
+
mode: 'twilio_standard',
|
|
202
|
+
language: 'en-US',
|
|
203
|
+
transcriptionProvider: 'Google',
|
|
204
|
+
ttsProvider: 'Google',
|
|
205
|
+
voice: 'Google.en-US-Journey-O',
|
|
206
|
+
fallbackToStandardOnError: false,
|
|
207
|
+
validationErrors: [],
|
|
208
|
+
})).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('returns false for profile with errors', () => {
|
|
212
|
+
expect(isVoiceProfileValid({
|
|
213
|
+
mode: 'twilio_elevenlabs_tts',
|
|
214
|
+
language: 'en-US',
|
|
215
|
+
transcriptionProvider: 'Google',
|
|
216
|
+
ttsProvider: 'ElevenLabs',
|
|
217
|
+
voice: '',
|
|
218
|
+
fallbackToStandardOnError: false,
|
|
219
|
+
validationErrors: ['voiceId is required'],
|
|
220
|
+
})).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
3
2
|
|
|
4
3
|
// No mock.module calls — this test file uses its own inline executeWebSearch
|
|
@@ -45,7 +44,7 @@ describe('WebSearchTool', () => {
|
|
|
45
44
|
globalThis.fetch = (async () => {
|
|
46
45
|
fetchCalled = true;
|
|
47
46
|
return new Response('{}', { status: 200 });
|
|
48
|
-
}) as
|
|
47
|
+
}) as unknown as typeof fetch;
|
|
49
48
|
|
|
50
49
|
process.env.BRAVE_API_KEY = 'test-key';
|
|
51
50
|
|
|
@@ -74,7 +73,7 @@ describe('WebSearchTool', () => {
|
|
|
74
73
|
status: 200,
|
|
75
74
|
headers: { 'Content-Type': 'application/json' },
|
|
76
75
|
});
|
|
77
|
-
}) as
|
|
76
|
+
}) as unknown as typeof fetch;
|
|
78
77
|
|
|
79
78
|
await executeWebSearch({ query: 'test', count: 50 }, 'test-key', 'brave');
|
|
80
79
|
expect(capturedUrl).toContain('count=20');
|
|
@@ -91,7 +90,7 @@ describe('WebSearchTool', () => {
|
|
|
91
90
|
status: 200,
|
|
92
91
|
headers: { 'Content-Type': 'application/json' },
|
|
93
92
|
});
|
|
94
|
-
}) as
|
|
93
|
+
}) as unknown as typeof fetch;
|
|
95
94
|
|
|
96
95
|
await executeWebSearch({ query: 'test', offset: 20 }, 'test-key', 'brave');
|
|
97
96
|
expect(capturedUrl).toContain('offset=9');
|
|
@@ -105,7 +104,7 @@ describe('WebSearchTool', () => {
|
|
|
105
104
|
status: 200,
|
|
106
105
|
headers: { 'Content-Type': 'application/json' },
|
|
107
106
|
});
|
|
108
|
-
}) as
|
|
107
|
+
}) as unknown as typeof fetch;
|
|
109
108
|
|
|
110
109
|
await executeWebSearch({ query: 'test', freshness: 'pw' }, 'test-key', 'brave');
|
|
111
110
|
expect(capturedUrl).toContain('freshness=pw');
|
|
@@ -119,7 +118,7 @@ describe('WebSearchTool', () => {
|
|
|
119
118
|
status: 200,
|
|
120
119
|
headers: { 'Content-Type': 'application/json' },
|
|
121
120
|
});
|
|
122
|
-
}) as
|
|
121
|
+
}) as unknown as typeof fetch;
|
|
123
122
|
|
|
124
123
|
await executeWebSearch({ query: 'test', freshness: 'invalid' }, 'test-key', 'brave');
|
|
125
124
|
expect(capturedUrl).not.toContain('freshness');
|
|
@@ -142,7 +141,7 @@ describe('WebSearchTool', () => {
|
|
|
142
141
|
status: 200,
|
|
143
142
|
headers: { 'Content-Type': 'application/json' },
|
|
144
143
|
})
|
|
145
|
-
) as
|
|
144
|
+
) as unknown as typeof fetch;
|
|
146
145
|
|
|
147
146
|
const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
|
|
148
147
|
expect(result.isError).toBe(false);
|
|
@@ -159,7 +158,7 @@ describe('WebSearchTool', () => {
|
|
|
159
158
|
status: 200,
|
|
160
159
|
headers: { 'Content-Type': 'application/json' },
|
|
161
160
|
})
|
|
162
|
-
) as
|
|
161
|
+
) as unknown as typeof fetch;
|
|
163
162
|
|
|
164
163
|
const result = await executeWebSearch({ query: 'noresults' }, 'test-key', 'brave');
|
|
165
164
|
expect(result.isError).toBe(false);
|
|
@@ -172,7 +171,7 @@ describe('WebSearchTool', () => {
|
|
|
172
171
|
status: 200,
|
|
173
172
|
headers: { 'Content-Type': 'application/json' },
|
|
174
173
|
})
|
|
175
|
-
) as
|
|
174
|
+
) as unknown as typeof fetch;
|
|
176
175
|
|
|
177
176
|
const result = await executeWebSearch({ query: 'empty' }, 'test-key', 'brave');
|
|
178
177
|
expect(result.isError).toBe(false);
|
|
@@ -182,7 +181,7 @@ describe('WebSearchTool', () => {
|
|
|
182
181
|
test('handles 401 unauthorized', async () => {
|
|
183
182
|
globalThis.fetch = (async () =>
|
|
184
183
|
new Response('Unauthorized', { status: 401 })
|
|
185
|
-
) as
|
|
184
|
+
) as unknown as typeof fetch;
|
|
186
185
|
|
|
187
186
|
const result = await executeWebSearch({ query: 'test' }, 'bad-key', 'brave');
|
|
188
187
|
expect(result.isError).toBe(true);
|
|
@@ -200,7 +199,7 @@ describe('WebSearchTool', () => {
|
|
|
200
199
|
JSON.stringify({ web: { results: [{ title: 'Result', url: 'https://example.com', description: 'Found it' }] } }),
|
|
201
200
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
202
201
|
);
|
|
203
|
-
}) as
|
|
202
|
+
}) as unknown as typeof fetch;
|
|
204
203
|
|
|
205
204
|
const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
|
|
206
205
|
expect(result.isError).toBe(false);
|
|
@@ -211,7 +210,7 @@ describe('WebSearchTool', () => {
|
|
|
211
210
|
test('returns error after exhausting 429 retries', async () => {
|
|
212
211
|
globalThis.fetch = (async () =>
|
|
213
212
|
new Response('Too Many Requests', { status: 429 })
|
|
214
|
-
) as
|
|
213
|
+
) as unknown as typeof fetch;
|
|
215
214
|
|
|
216
215
|
const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
|
|
217
216
|
expect(result.isError).toBe(true);
|
|
@@ -235,7 +234,7 @@ describe('WebSearchTool', () => {
|
|
|
235
234
|
JSON.stringify({ web: { results: [] } }),
|
|
236
235
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
237
236
|
);
|
|
238
|
-
}) as
|
|
237
|
+
}) as unknown as typeof fetch;
|
|
239
238
|
|
|
240
239
|
const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
|
|
241
240
|
expect(result.isError).toBe(false);
|
|
@@ -245,7 +244,7 @@ describe('WebSearchTool', () => {
|
|
|
245
244
|
test('handles network errors', async () => {
|
|
246
245
|
globalThis.fetch = (async () => {
|
|
247
246
|
throw new Error('Network unreachable');
|
|
248
|
-
}) as
|
|
247
|
+
}) as unknown as typeof fetch;
|
|
249
248
|
|
|
250
249
|
const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
|
|
251
250
|
expect(result.isError).toBe(true);
|
|
@@ -254,7 +253,7 @@ describe('WebSearchTool', () => {
|
|
|
254
253
|
|
|
255
254
|
test('sends correct headers', async () => {
|
|
256
255
|
let capturedHeaders: Record<string, string> = {};
|
|
257
|
-
globalThis.fetch = (async (_url: string, init:
|
|
256
|
+
globalThis.fetch = (async (_url: string, init: RequestInit) => {
|
|
258
257
|
capturedHeaders = Object.fromEntries(
|
|
259
258
|
Object.entries(init.headers as Record<string, string>),
|
|
260
259
|
);
|
|
@@ -262,7 +261,7 @@ describe('WebSearchTool', () => {
|
|
|
262
261
|
status: 200,
|
|
263
262
|
headers: { 'Content-Type': 'application/json' },
|
|
264
263
|
});
|
|
265
|
-
}) as
|
|
264
|
+
}) as unknown as typeof fetch;
|
|
266
265
|
|
|
267
266
|
await executeWebSearch({ query: 'test' }, 'my-api-key', 'brave');
|
|
268
267
|
expect(capturedHeaders['X-Subscription-Token']).toBe('my-api-key');
|
|
@@ -288,7 +287,7 @@ describe('WebSearchTool', () => {
|
|
|
288
287
|
status: 200,
|
|
289
288
|
headers: { 'Content-Type': 'application/json' },
|
|
290
289
|
})
|
|
291
|
-
) as
|
|
290
|
+
) as unknown as typeof fetch;
|
|
292
291
|
|
|
293
292
|
const result = await executeWebSearch({ query: 'test' }, 'test-key', 'brave');
|
|
294
293
|
expect(result.isError).toBe(false);
|
|
@@ -309,7 +308,7 @@ describe('WebSearchTool', () => {
|
|
|
309
308
|
status: 200,
|
|
310
309
|
headers: { 'Content-Type': 'application/json' },
|
|
311
310
|
})
|
|
312
|
-
) as
|
|
311
|
+
) as unknown as typeof fetch;
|
|
313
312
|
|
|
314
313
|
const result = await executeWebSearch({ query: 'test' }, 'pplx-key', 'perplexity');
|
|
315
314
|
expect(result.isError).toBe(false);
|
|
@@ -328,7 +327,7 @@ describe('WebSearchTool', () => {
|
|
|
328
327
|
status: 200,
|
|
329
328
|
headers: { 'Content-Type': 'application/json' },
|
|
330
329
|
})
|
|
331
|
-
) as
|
|
330
|
+
) as unknown as typeof fetch;
|
|
332
331
|
|
|
333
332
|
const result = await executeWebSearch({ query: 'noresults' }, 'pplx-key', 'perplexity');
|
|
334
333
|
expect(result.isError).toBe(false);
|
|
@@ -338,7 +337,7 @@ describe('WebSearchTool', () => {
|
|
|
338
337
|
test('handles 401 unauthorized', async () => {
|
|
339
338
|
globalThis.fetch = (async () =>
|
|
340
339
|
new Response('Unauthorized', { status: 401 })
|
|
341
|
-
) as
|
|
340
|
+
) as unknown as typeof fetch;
|
|
342
341
|
|
|
343
342
|
const result = await executeWebSearch({ query: 'test' }, 'bad-key', 'perplexity');
|
|
344
343
|
expect(result.isError).toBe(true);
|
|
@@ -347,23 +346,23 @@ describe('WebSearchTool', () => {
|
|
|
347
346
|
|
|
348
347
|
test('sends correct headers', async () => {
|
|
349
348
|
let capturedHeaders: Record<string, string> = {};
|
|
350
|
-
let capturedBody:
|
|
351
|
-
globalThis.fetch = (async (_url: string, init:
|
|
349
|
+
let capturedBody: Record<string, unknown> = {};
|
|
350
|
+
globalThis.fetch = (async (_url: string, init: RequestInit) => {
|
|
352
351
|
capturedHeaders = Object.fromEntries(
|
|
353
352
|
Object.entries(init.headers as Record<string, string>),
|
|
354
353
|
);
|
|
355
|
-
capturedBody = JSON.parse(init.body);
|
|
354
|
+
capturedBody = JSON.parse(init.body as string);
|
|
356
355
|
return new Response(JSON.stringify({ choices: [{ message: { content: 'result' } }] }), {
|
|
357
356
|
status: 200,
|
|
358
357
|
headers: { 'Content-Type': 'application/json' },
|
|
359
358
|
});
|
|
360
|
-
}) as
|
|
359
|
+
}) as unknown as typeof fetch;
|
|
361
360
|
|
|
362
361
|
await executeWebSearch({ query: 'test query' }, 'pplx-my-key', 'perplexity');
|
|
363
362
|
expect(capturedHeaders['Authorization']).toBe('Bearer pplx-my-key');
|
|
364
363
|
expect(capturedHeaders['Content-Type']).toBe('application/json');
|
|
365
364
|
expect(capturedBody.model).toBe('sonar');
|
|
366
|
-
expect(capturedBody.messages[0].content).toBe('test query');
|
|
365
|
+
expect((capturedBody.messages as Array<{ content: string }>)[0].content).toBe('test query');
|
|
367
366
|
});
|
|
368
367
|
|
|
369
368
|
test('retries on 429 and succeeds', async () => {
|
|
@@ -377,7 +376,7 @@ describe('WebSearchTool', () => {
|
|
|
377
376
|
JSON.stringify({ choices: [{ message: { content: 'Found it' } }] }),
|
|
378
377
|
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
379
378
|
);
|
|
380
|
-
}) as
|
|
379
|
+
}) as unknown as typeof fetch;
|
|
381
380
|
|
|
382
381
|
const result = await executeWebSearch({ query: 'test' }, 'pplx-key', 'perplexity');
|
|
383
382
|
expect(result.isError).toBe(false);
|
|
@@ -388,7 +387,7 @@ describe('WebSearchTool', () => {
|
|
|
388
387
|
test('handles network errors', async () => {
|
|
389
388
|
globalThis.fetch = (async () => {
|
|
390
389
|
throw new Error('Network unreachable');
|
|
391
|
-
}) as
|
|
390
|
+
}) as unknown as typeof fetch;
|
|
392
391
|
|
|
393
392
|
const result = await executeWebSearch({ query: 'test' }, 'pplx-key', 'perplexity');
|
|
394
393
|
expect(result.isError).toBe(true);
|
|
@@ -397,6 +396,23 @@ describe('WebSearchTool', () => {
|
|
|
397
396
|
});
|
|
398
397
|
});
|
|
399
398
|
|
|
399
|
+
interface BraveSearchResult {
|
|
400
|
+
title: string;
|
|
401
|
+
url: string;
|
|
402
|
+
description?: string;
|
|
403
|
+
age?: string;
|
|
404
|
+
extra_snippets?: string[];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
interface BraveSearchResponse {
|
|
408
|
+
web?: { results: BraveSearchResult[] };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
interface PerplexityResponse {
|
|
412
|
+
choices?: Array<{ message: { content: string } }>;
|
|
413
|
+
citations?: string[];
|
|
414
|
+
}
|
|
415
|
+
|
|
400
416
|
/**
|
|
401
417
|
* Helper that exercises the web search logic directly, bypassing module
|
|
402
418
|
* registration concerns. This replicates the core execute path from
|
|
@@ -414,7 +430,7 @@ async function executeWebSearch(
|
|
|
414
430
|
|
|
415
431
|
if (!apiKey) {
|
|
416
432
|
return {
|
|
417
|
-
content: 'Error: No web search API key configured.
|
|
433
|
+
content: 'Error: No web search API key configured. Provide a PERPLEXITY_API_KEY or BRAVE_API_KEY here in the chat using the secure credential prompt, or set it from the Settings page.',
|
|
418
434
|
isError: true,
|
|
419
435
|
};
|
|
420
436
|
}
|
|
@@ -461,7 +477,7 @@ async function executeBraveSearchHelper(
|
|
|
461
477
|
});
|
|
462
478
|
|
|
463
479
|
if (response.ok) {
|
|
464
|
-
const data = await response.json() as
|
|
480
|
+
const data = await response.json() as BraveSearchResponse;
|
|
465
481
|
const results = data.web?.results ?? [];
|
|
466
482
|
|
|
467
483
|
if (results.length === 0) {
|
|
@@ -536,7 +552,7 @@ async function executePerplexitySearchHelper(
|
|
|
536
552
|
});
|
|
537
553
|
|
|
538
554
|
if (response.ok) {
|
|
539
|
-
const data = await response.json() as
|
|
555
|
+
const data = await response.json() as PerplexityResponse;
|
|
540
556
|
const content = data.choices?.[0]?.message?.content;
|
|
541
557
|
if (!content) {
|
|
542
558
|
return { content: `No results found for "${query}".`, isError: false };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import type { Database } from 'bun:sqlite';
|
|
4
|
+
import * as net from 'node:net';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
|
|
8
|
+
const testDir = mkdtempSync(join(tmpdir(), 'work-item-output-test-'));
|
|
9
|
+
|
|
10
|
+
mock.module('../util/platform.js', () => ({
|
|
11
|
+
getDataDir: () => testDir,
|
|
12
|
+
isMacOS: () => process.platform === 'darwin',
|
|
13
|
+
isLinux: () => process.platform === 'linux',
|
|
14
|
+
isWindows: () => process.platform === 'win32',
|
|
15
|
+
getSocketPath: () => join(testDir, 'test.sock'),
|
|
16
|
+
getPidPath: () => join(testDir, 'test.pid'),
|
|
17
|
+
getDbPath: () => join(testDir, 'test.db'),
|
|
18
|
+
getLogPath: () => join(testDir, 'test.log'),
|
|
19
|
+
ensureDataDir: () => {},
|
|
20
|
+
migrateToDataLayout: () => {},
|
|
21
|
+
migrateToWorkspaceLayout: () => {},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
mock.module('../util/logger.js', () => ({
|
|
25
|
+
getLogger: () => new Proxy({} as Record<string, unknown>, {
|
|
26
|
+
get: () => () => {},
|
|
27
|
+
}),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
mock.module('../config/loader.js', () => ({
|
|
31
|
+
getConfig: () => ({ memory: {} }),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
mock.module('./indexer.js', () => ({
|
|
35
|
+
indexMessageNow: () => {},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
import { addMessage, createConversation } from '../memory/conversation-store.js';
|
|
39
|
+
import { getDb, initializeDb, resetDb } from '../memory/db.js';
|
|
40
|
+
import { createTask, createTaskRun, updateTaskRun } from '../tasks/task-store.js';
|
|
41
|
+
import { createWorkItem, updateWorkItem } from '../work-items/work-item-store.js';
|
|
42
|
+
import { handleWorkItemOutput } from '../daemon/handlers/work-items.js';
|
|
43
|
+
import type { HandlerContext } from '../daemon/handlers/shared.js';
|
|
44
|
+
|
|
45
|
+
initializeDb();
|
|
46
|
+
|
|
47
|
+
afterAll(() => {
|
|
48
|
+
resetDb();
|
|
49
|
+
try { rmSync(testDir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function getRawDb(): Database {
|
|
53
|
+
return (getDb() as unknown as { $client: Database }).$client;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('handleWorkItemOutput', () => {
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
const raw = getRawDb();
|
|
59
|
+
raw.run('DELETE FROM task_runs');
|
|
60
|
+
raw.run('DELETE FROM work_items');
|
|
61
|
+
raw.run('DELETE FROM tasks');
|
|
62
|
+
raw.run('DELETE FROM messages');
|
|
63
|
+
raw.run('DELETE FROM conversations');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('uses only the latest assistant text block for summary output', () => {
|
|
67
|
+
const task = createTask({
|
|
68
|
+
title: 'Delete weather report',
|
|
69
|
+
template: 'Delete weather_report_task.txt',
|
|
70
|
+
});
|
|
71
|
+
const run = createTaskRun(task.id);
|
|
72
|
+
const item = createWorkItem({
|
|
73
|
+
taskId: task.id,
|
|
74
|
+
title: 'Delete weather_report_task.txt',
|
|
75
|
+
});
|
|
76
|
+
const conversation = createConversation('Task output test');
|
|
77
|
+
|
|
78
|
+
updateTaskRun(run.id, {
|
|
79
|
+
status: 'completed',
|
|
80
|
+
conversationId: conversation.id,
|
|
81
|
+
finishedAt: Date.now(),
|
|
82
|
+
});
|
|
83
|
+
updateWorkItem(item.id, {
|
|
84
|
+
status: 'awaiting_review',
|
|
85
|
+
lastRunId: run.id,
|
|
86
|
+
lastRunConversationId: conversation.id,
|
|
87
|
+
lastRunStatus: 'completed',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
addMessage(conversation.id, 'assistant', JSON.stringify([
|
|
91
|
+
{ type: 'text', text: "I'll need to delete the weather report file from your Documents folder. This will permanently remove it." },
|
|
92
|
+
{ type: 'text', text: "Looks like that file has already been deleted — it's no longer there. I'll mark this task as done." },
|
|
93
|
+
{ type: 'text', text: 'The file is already deleted, so the task is complete.' },
|
|
94
|
+
]));
|
|
95
|
+
|
|
96
|
+
const sent: Array<{ type: string; [key: string]: unknown }> = [];
|
|
97
|
+
const socket = {} as net.Socket;
|
|
98
|
+
const ctx = {
|
|
99
|
+
send: (_socket: net.Socket, msg: { type: string; [key: string]: unknown }) => sent.push(msg),
|
|
100
|
+
} as unknown as HandlerContext;
|
|
101
|
+
|
|
102
|
+
handleWorkItemOutput({ type: 'work_item_output', id: item.id }, socket, ctx);
|
|
103
|
+
|
|
104
|
+
expect(sent).toHaveLength(1);
|
|
105
|
+
const response = sent[0];
|
|
106
|
+
expect(response.type).toBe('work_item_output_response');
|
|
107
|
+
expect(response.success).toBe(true);
|
|
108
|
+
expect((response.output as { summary: string }).summary).toBe('The file is already deleted, so the task is complete.');
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/agent/loop.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type AgentEvent =
|
|
|
35
35
|
| { type: 'usage'; inputTokens: number; outputTokens: number; cacheCreationInputTokens?: number; cacheReadInputTokens?: number; model: string; providerDurationMs: number; rawRequest?: unknown; rawResponse?: unknown };
|
|
36
36
|
|
|
37
37
|
const DEFAULT_CONFIG: AgentLoopConfig = {
|
|
38
|
-
maxTokens:
|
|
38
|
+
maxTokens: 16000,
|
|
39
39
|
maxToolUseTurns: 30,
|
|
40
40
|
};
|
|
41
41
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readTextFileSync } from '../util/fs.js';
|
|
2
2
|
import { getLogger } from '../util/logger.js';
|
|
3
3
|
import { getWorkspacePromptPath } from '../util/platform.js';
|
|
4
4
|
import { getConfig } from '../config/loader.js';
|
|
@@ -115,15 +115,7 @@ export class AgentHeartbeatService {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
private readChecklist(): string {
|
|
118
|
-
|
|
119
|
-
if (existsSync(heartbeatPath)) {
|
|
120
|
-
try {
|
|
121
|
-
return readFileSync(heartbeatPath, 'utf-8');
|
|
122
|
-
} catch (err) {
|
|
123
|
-
log.warn({ err, heartbeatPath }, 'Failed to read HEARTBEAT.md, using default checklist');
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return DEFAULT_CHECKLIST;
|
|
118
|
+
return readTextFileSync(getWorkspacePromptPath('HEARTBEAT.md')) ?? DEFAULT_CHECKLIST;
|
|
127
119
|
}
|
|
128
120
|
|
|
129
121
|
/** @internal Exposed for testing. */
|