@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,324 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
2
|
+
import { ChannelReadinessService, REMOTE_TTL_MS } from '../runtime/channel-readiness-service.js';
|
|
3
|
+
import type { ChannelId } from '../channels/types.js';
|
|
4
|
+
import type { ChannelProbe, ReadinessCheckResult } from '../runtime/channel-readiness-types.js';
|
|
5
|
+
|
|
6
|
+
// ── Test helpers ────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
function makeProbe(
|
|
9
|
+
channel: ChannelId,
|
|
10
|
+
localResults: ReadinessCheckResult[],
|
|
11
|
+
remoteResults?: ReadinessCheckResult[],
|
|
12
|
+
): ChannelProbe & { localCallCount: number; remoteCallCount: number } {
|
|
13
|
+
const probe = {
|
|
14
|
+
channel,
|
|
15
|
+
localCallCount: 0,
|
|
16
|
+
remoteCallCount: 0,
|
|
17
|
+
runLocalChecks(): ReadinessCheckResult[] {
|
|
18
|
+
probe.localCallCount++;
|
|
19
|
+
return localResults;
|
|
20
|
+
},
|
|
21
|
+
...(remoteResults !== undefined
|
|
22
|
+
? {
|
|
23
|
+
async runRemoteChecks(): Promise<ReadinessCheckResult[]> {
|
|
24
|
+
probe.remoteCallCount++;
|
|
25
|
+
return remoteResults;
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
: {}),
|
|
29
|
+
};
|
|
30
|
+
return probe;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Tests ───────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
describe('ChannelReadinessService', () => {
|
|
36
|
+
let service: ChannelReadinessService;
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
service = new ChannelReadinessService();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('local checks run on every call (no caching of local results)', async () => {
|
|
43
|
+
const probe = makeProbe('sms', [
|
|
44
|
+
{ name: 'creds', passed: true, message: 'ok' },
|
|
45
|
+
]);
|
|
46
|
+
service.registerProbe(probe);
|
|
47
|
+
|
|
48
|
+
await service.getReadiness('sms');
|
|
49
|
+
await service.getReadiness('sms');
|
|
50
|
+
|
|
51
|
+
expect(probe.localCallCount).toBe(2);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('cache miss runs local checks and returns snapshot', async () => {
|
|
55
|
+
const probe = makeProbe('sms', [
|
|
56
|
+
{ name: 'creds', passed: true, message: 'ok' },
|
|
57
|
+
{ name: 'phone', passed: false, message: 'missing' },
|
|
58
|
+
]);
|
|
59
|
+
service.registerProbe(probe);
|
|
60
|
+
|
|
61
|
+
const [snapshot] = await service.getReadiness('sms');
|
|
62
|
+
|
|
63
|
+
expect(probe.localCallCount).toBe(1);
|
|
64
|
+
expect(snapshot.channel).toBe('sms');
|
|
65
|
+
expect(snapshot.ready).toBe(false);
|
|
66
|
+
expect(snapshot.localChecks).toHaveLength(2);
|
|
67
|
+
expect(snapshot.reasons).toEqual([
|
|
68
|
+
{ code: 'phone', text: 'missing' },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('includeRemote=true runs remote checks on cache miss', async () => {
|
|
73
|
+
const probe = makeProbe(
|
|
74
|
+
'sms',
|
|
75
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
76
|
+
[{ name: 'api_check', passed: true, message: 'remote ok' }],
|
|
77
|
+
);
|
|
78
|
+
service.registerProbe(probe);
|
|
79
|
+
|
|
80
|
+
const [snapshot] = await service.getReadiness('sms', true);
|
|
81
|
+
|
|
82
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
83
|
+
expect(snapshot.remoteChecks).toHaveLength(1);
|
|
84
|
+
expect(snapshot.remoteChecks![0].passed).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('cached remote checks reused within TTL', async () => {
|
|
88
|
+
const probe = makeProbe(
|
|
89
|
+
'sms',
|
|
90
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
91
|
+
[{ name: 'api_check', passed: true, message: 'remote ok' }],
|
|
92
|
+
);
|
|
93
|
+
service.registerProbe(probe);
|
|
94
|
+
|
|
95
|
+
// First call populates cache
|
|
96
|
+
await service.getReadiness('sms', true);
|
|
97
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
98
|
+
|
|
99
|
+
// Second call within TTL should reuse cache
|
|
100
|
+
const [snapshot] = await service.getReadiness('sms', true);
|
|
101
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
102
|
+
expect(snapshot.remoteChecks).toHaveLength(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('stale cache triggers remote check re-run', async () => {
|
|
106
|
+
const probe = makeProbe(
|
|
107
|
+
'sms',
|
|
108
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
109
|
+
[{ name: 'api_check', passed: true, message: 'remote ok' }],
|
|
110
|
+
);
|
|
111
|
+
service.registerProbe(probe);
|
|
112
|
+
|
|
113
|
+
// First call
|
|
114
|
+
await service.getReadiness('sms', true);
|
|
115
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
116
|
+
|
|
117
|
+
// Manually age the cached snapshot beyond TTL
|
|
118
|
+
const cached = (service as unknown as { snapshots: Map<string, { checkedAt: number }> }).snapshots.get('sms::__default__')!;
|
|
119
|
+
cached.checkedAt = Date.now() - REMOTE_TTL_MS - 1;
|
|
120
|
+
|
|
121
|
+
// Second call should re-run remote checks
|
|
122
|
+
await service.getReadiness('sms', true);
|
|
123
|
+
expect(probe.remoteCallCount).toBe(2);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('invalidateChannel clears cache for specific channel', async () => {
|
|
127
|
+
const probe = makeProbe(
|
|
128
|
+
'sms',
|
|
129
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
130
|
+
[{ name: 'api_check', passed: true, message: 'remote ok' }],
|
|
131
|
+
);
|
|
132
|
+
service.registerProbe(probe);
|
|
133
|
+
|
|
134
|
+
await service.getReadiness('sms', true);
|
|
135
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
136
|
+
|
|
137
|
+
service.invalidateChannel('sms');
|
|
138
|
+
|
|
139
|
+
// After invalidation, remote checks should run again
|
|
140
|
+
await service.getReadiness('sms', true);
|
|
141
|
+
expect(probe.remoteCallCount).toBe(2);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('invalidateAll clears all cached snapshots', async () => {
|
|
145
|
+
const smsProbe = makeProbe(
|
|
146
|
+
'sms',
|
|
147
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
148
|
+
[{ name: 'api', passed: true, message: 'ok' }],
|
|
149
|
+
);
|
|
150
|
+
const telegramProbe = makeProbe(
|
|
151
|
+
'telegram',
|
|
152
|
+
[{ name: 'token', passed: true, message: 'ok' }],
|
|
153
|
+
[{ name: 'webhook', passed: true, message: 'ok' }],
|
|
154
|
+
);
|
|
155
|
+
service.registerProbe(smsProbe);
|
|
156
|
+
service.registerProbe(telegramProbe);
|
|
157
|
+
|
|
158
|
+
await service.getReadiness(undefined, true);
|
|
159
|
+
expect(smsProbe.remoteCallCount).toBe(1);
|
|
160
|
+
expect(telegramProbe.remoteCallCount).toBe(1);
|
|
161
|
+
|
|
162
|
+
service.invalidateAll();
|
|
163
|
+
|
|
164
|
+
await service.getReadiness(undefined, true);
|
|
165
|
+
expect(smsProbe.remoteCallCount).toBe(2);
|
|
166
|
+
expect(telegramProbe.remoteCallCount).toBe(2);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('unknown channel returns unsupported_channel reason', async () => {
|
|
170
|
+
// Cast to exercise runtime handling of an unrecognized channel value
|
|
171
|
+
const [snapshot] = await service.getReadiness('carrier_pigeon' as ChannelId);
|
|
172
|
+
|
|
173
|
+
expect(snapshot.channel).toBe('carrier_pigeon' as ChannelId);
|
|
174
|
+
expect(snapshot.ready).toBe(false);
|
|
175
|
+
expect(snapshot.reasons).toEqual([
|
|
176
|
+
{ code: 'unsupported_channel', text: 'Channel carrier_pigeon is not supported' },
|
|
177
|
+
]);
|
|
178
|
+
expect(snapshot.localChecks).toHaveLength(0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('all checks passing yields ready=true', async () => {
|
|
182
|
+
const probe = makeProbe('telegram', [
|
|
183
|
+
{ name: 'a', passed: true, message: 'ok' },
|
|
184
|
+
{ name: 'b', passed: true, message: 'ok' },
|
|
185
|
+
]);
|
|
186
|
+
service.registerProbe(probe);
|
|
187
|
+
|
|
188
|
+
const [snapshot] = await service.getReadiness('telegram');
|
|
189
|
+
|
|
190
|
+
expect(snapshot.ready).toBe(true);
|
|
191
|
+
expect(snapshot.reasons).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('getReadiness with no channel returns all registered channels', async () => {
|
|
195
|
+
service.registerProbe(makeProbe('sms', [{ name: 'a', passed: true, message: 'ok' }]));
|
|
196
|
+
service.registerProbe(makeProbe('telegram', [{ name: 'b', passed: true, message: 'ok' }]));
|
|
197
|
+
|
|
198
|
+
const snapshots = await service.getReadiness();
|
|
199
|
+
|
|
200
|
+
expect(snapshots).toHaveLength(2);
|
|
201
|
+
const channels = snapshots.map((s) => s.channel).sort();
|
|
202
|
+
expect(channels).toEqual(['sms', 'telegram']);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('cached remote checks preserve original checkedAt (TTL not reset on reuse)', async () => {
|
|
206
|
+
const probe = makeProbe(
|
|
207
|
+
'sms',
|
|
208
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
209
|
+
[{ name: 'api_check', passed: true, message: 'remote ok' }],
|
|
210
|
+
);
|
|
211
|
+
service.registerProbe(probe);
|
|
212
|
+
|
|
213
|
+
// First call populates cache with freshly fetched remote checks
|
|
214
|
+
const [first] = await service.getReadiness('sms', true);
|
|
215
|
+
const originalCheckedAt = first.checkedAt;
|
|
216
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
217
|
+
|
|
218
|
+
// Second call within TTL reuses cache — checkedAt must stay at the original value
|
|
219
|
+
const [second] = await service.getReadiness('sms', true);
|
|
220
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
221
|
+
expect(second.checkedAt).toBe(originalCheckedAt);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('includeRemote runs remote checks when cache exists without remote data', async () => {
|
|
225
|
+
const probe = makeProbe(
|
|
226
|
+
'sms',
|
|
227
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
228
|
+
[{ name: 'api_check', passed: true, message: 'remote ok' }],
|
|
229
|
+
);
|
|
230
|
+
service.registerProbe(probe);
|
|
231
|
+
|
|
232
|
+
// First call without includeRemote — cache has no remote data
|
|
233
|
+
await service.getReadiness('sms', false);
|
|
234
|
+
expect(probe.remoteCallCount).toBe(0);
|
|
235
|
+
|
|
236
|
+
// Second call with includeRemote — should run remote checks even though
|
|
237
|
+
// the cached snapshot exists (because it has no remoteChecks)
|
|
238
|
+
const [snapshot] = await service.getReadiness('sms', true);
|
|
239
|
+
expect(probe.remoteCallCount).toBe(1);
|
|
240
|
+
expect(snapshot.remoteChecks).toHaveLength(1);
|
|
241
|
+
expect(snapshot.remoteChecks![0].passed).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('failed remote check makes channel not ready', async () => {
|
|
245
|
+
const probe = makeProbe(
|
|
246
|
+
'sms',
|
|
247
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
248
|
+
[{ name: 'api_check', passed: false, message: 'API unreachable' }],
|
|
249
|
+
);
|
|
250
|
+
service.registerProbe(probe);
|
|
251
|
+
|
|
252
|
+
const [snapshot] = await service.getReadiness('sms', true);
|
|
253
|
+
|
|
254
|
+
expect(snapshot.ready).toBe(false);
|
|
255
|
+
expect(snapshot.reasons).toEqual([
|
|
256
|
+
{ code: 'api_check', text: 'API unreachable' },
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('fresh cached remote failures do not affect local-only readiness', async () => {
|
|
261
|
+
const probe = makeProbe(
|
|
262
|
+
'sms',
|
|
263
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
264
|
+
[{ name: 'api_check', passed: false, message: 'API unreachable' }],
|
|
265
|
+
);
|
|
266
|
+
service.registerProbe(probe);
|
|
267
|
+
|
|
268
|
+
// Prime remote cache with a failing check
|
|
269
|
+
await service.getReadiness('sms', true);
|
|
270
|
+
|
|
271
|
+
// Immediately call with includeRemote=false (cache is still fresh within TTL).
|
|
272
|
+
// The cached remote failure should be surfaced for visibility but must NOT
|
|
273
|
+
// affect readiness when the caller explicitly opted out of remote checks.
|
|
274
|
+
const [snapshot] = await service.getReadiness('sms', false);
|
|
275
|
+
expect(snapshot.ready).toBe(true);
|
|
276
|
+
expect(snapshot.reasons).toEqual([]);
|
|
277
|
+
// Remote checks are still visible for informational purposes
|
|
278
|
+
expect(snapshot.remoteChecks).toHaveLength(1);
|
|
279
|
+
expect(snapshot.remoteChecks![0].passed).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('stale cached remote failures do not affect local-only readiness', async () => {
|
|
283
|
+
const probe = makeProbe(
|
|
284
|
+
'sms',
|
|
285
|
+
[{ name: 'creds', passed: true, message: 'ok' }],
|
|
286
|
+
[{ name: 'api_check', passed: false, message: 'API unreachable' }],
|
|
287
|
+
);
|
|
288
|
+
service.registerProbe(probe);
|
|
289
|
+
|
|
290
|
+
// Prime remote cache with a failing check
|
|
291
|
+
await service.getReadiness('sms', true);
|
|
292
|
+
|
|
293
|
+
// Age snapshot beyond TTL so remote checks are stale
|
|
294
|
+
const cached = (service as unknown as { snapshots: Map<string, { checkedAt: number }> }).snapshots.get('sms::__default__')!;
|
|
295
|
+
cached.checkedAt = Date.now() - REMOTE_TTL_MS - 1;
|
|
296
|
+
|
|
297
|
+
// Local-only call should not be blocked by stale remote failure
|
|
298
|
+
const [snapshot] = await service.getReadiness('sms', false);
|
|
299
|
+
expect(snapshot.stale).toBe(true);
|
|
300
|
+
expect(snapshot.ready).toBe(true);
|
|
301
|
+
expect(snapshot.reasons).toEqual([]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('remote cache is scoped per assistantId', async () => {
|
|
305
|
+
const remoteCalls: Record<string, number> = {};
|
|
306
|
+
const probe: ChannelProbe = {
|
|
307
|
+
channel: 'sms',
|
|
308
|
+
runLocalChecks: () => [{ name: 'local', passed: true, message: 'ok' }],
|
|
309
|
+
async runRemoteChecks(context) {
|
|
310
|
+
const key = context?.assistantId ?? '__default__';
|
|
311
|
+
remoteCalls[key] = (remoteCalls[key] ?? 0) + 1;
|
|
312
|
+
return [{ name: 'remote', passed: true, message: `ok-${key}` }];
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
service.registerProbe(probe);
|
|
316
|
+
|
|
317
|
+
await service.getReadiness('sms', true, 'ast-alpha');
|
|
318
|
+
await service.getReadiness('sms', true, 'ast-beta');
|
|
319
|
+
await service.getReadiness('sms', true, 'ast-alpha');
|
|
320
|
+
|
|
321
|
+
expect(remoteCalls['ast-alpha']).toBe(1);
|
|
322
|
+
expect(remoteCalls['ast-beta']).toBe(1);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Smoke command (run all security test files together):
|
|
2
2
|
// bun test src/__tests__/checker.test.ts src/__tests__/trust-store.test.ts src/__tests__/session-skill-tools.test.ts src/__tests__/skill-script-runner-host.test.ts
|
|
3
3
|
|
|
4
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
4
|
import { describe, test, expect, beforeAll, beforeEach, afterEach, mock } from 'bun:test';
|
|
6
5
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, symlinkSync, realpathSync } from 'node:fs';
|
|
7
6
|
import { tmpdir, homedir } from 'node:os';
|
|
@@ -39,9 +38,16 @@ mock.module('../util/logger.js', () => ({
|
|
|
39
38
|
|
|
40
39
|
// Mutable config object so tests can switch permissions.mode between
|
|
41
40
|
// 'legacy', 'strict', and 'workspace' without re-registering the mock.
|
|
42
|
-
|
|
43
|
-
permissions: { mode: 'legacy'
|
|
44
|
-
skills: { load: { extraDirs:
|
|
41
|
+
interface TestConfig {
|
|
42
|
+
permissions: { mode: 'legacy' | 'strict' | 'workspace' };
|
|
43
|
+
skills: { load: { extraDirs: string[] } };
|
|
44
|
+
sandbox: { enabled: boolean };
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const testConfig: TestConfig = {
|
|
49
|
+
permissions: { mode: 'legacy' },
|
|
50
|
+
skills: { load: { extraDirs: [] } },
|
|
45
51
|
sandbox: { enabled: true },
|
|
46
52
|
};
|
|
47
53
|
|
|
@@ -58,6 +64,7 @@ mock.module('../config/loader.js', () => ({
|
|
|
58
64
|
|
|
59
65
|
import { classifyRisk, check, generateAllowlistOptions, generateScopeOptions, _resetLegacyDeprecationWarning } from '../permissions/checker.js';
|
|
60
66
|
import { RiskLevel } from '../permissions/types.js';
|
|
67
|
+
import type { TrustRule } from '../permissions/types.js';
|
|
61
68
|
import { addRule, clearCache, findHighestPriorityRule } from '../permissions/trust-store.js';
|
|
62
69
|
import { getDefaultRuleTemplates } from '../permissions/defaults.js';
|
|
63
70
|
import { registerTool, getTool } from '../tools/registry.js';
|
|
@@ -2353,13 +2360,13 @@ describe('Permission Checker', () => {
|
|
|
2353
2360
|
const trustDir = dirnameFn(trustPath);
|
|
2354
2361
|
if (!existsSync(trustDir)) mkdirSyncFs(trustDir, { recursive: true });
|
|
2355
2362
|
|
|
2356
|
-
let currentRules:
|
|
2363
|
+
let currentRules: TrustRule[] = [];
|
|
2357
2364
|
try {
|
|
2358
2365
|
const raw = readFileSync(trustPath, 'utf-8');
|
|
2359
2366
|
currentRules = JSON.parse(raw).rules ?? [];
|
|
2360
2367
|
} catch { /* first run */ }
|
|
2361
2368
|
|
|
2362
|
-
currentRules = currentRules.filter((r:
|
|
2369
|
+
currentRules = currentRules.filter((r: TrustRule) => r.id !== opts.id);
|
|
2363
2370
|
currentRules.push({
|
|
2364
2371
|
...opts,
|
|
2365
2372
|
createdAt: Date.now(),
|
|
@@ -2486,7 +2493,7 @@ describe('Permission Checker', () => {
|
|
|
2486
2493
|
// Write the executionTarget field directly (addVersionBoundRule doesn't support it)
|
|
2487
2494
|
const trustPath = join(checkerTestDir, 'protected', 'trust.json');
|
|
2488
2495
|
const raw = JSON.parse((await import('node:fs')).readFileSync(trustPath, 'utf-8'));
|
|
2489
|
-
const rule = raw.rules.find((r:
|
|
2496
|
+
const rule = raw.rules.find((r: TrustRule) => r.id === 'inv4-target-scoped');
|
|
2490
2497
|
rule.executionTarget = '/usr/local/bin/node';
|
|
2491
2498
|
(await import('node:fs')).writeFileSync(trustPath, JSON.stringify(raw, null, 2));
|
|
2492
2499
|
clearCache();
|
|
@@ -6,33 +6,53 @@ let llmResolution: 'keep_existing' | 'keep_candidate' | 'merge' | 'still_unclear
|
|
|
6
6
|
let llmResolvedStatement = '';
|
|
7
7
|
let llmExplanation = 'Unclear response from user.';
|
|
8
8
|
|
|
9
|
-
mock.module('
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
mock.module('../providers/provider-send-message.js', () => ({
|
|
10
|
+
getConfiguredProvider: () => ({
|
|
11
|
+
sendMessage: async (
|
|
12
|
+
_messages: unknown,
|
|
13
|
+
_tools: unknown,
|
|
14
|
+
_system: unknown,
|
|
15
|
+
opts?: { signal?: AbortSignal },
|
|
16
|
+
) => {
|
|
17
|
+
llmCallCount += 1;
|
|
18
|
+
if (llmDelayMs > 0) {
|
|
19
|
+
await new Promise((resolve, reject) => {
|
|
20
|
+
const timer = setTimeout(resolve, llmDelayMs);
|
|
21
|
+
opts?.signal?.addEventListener('abort', () => {
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
reject(new Error('Request was aborted.'));
|
|
21
24
|
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
content: [{
|
|
29
|
+
type: 'tool_use' as const,
|
|
30
|
+
id: 'test-tool-use-id',
|
|
31
|
+
name: 'resolve_conflict',
|
|
32
|
+
input: {
|
|
33
|
+
resolution: llmResolution,
|
|
34
|
+
resolved_statement: llmResolvedStatement,
|
|
35
|
+
explanation: llmExplanation,
|
|
36
|
+
},
|
|
37
|
+
}],
|
|
38
|
+
model: 'claude-haiku-4-5-20251001',
|
|
39
|
+
stopReason: 'tool_use',
|
|
40
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
createTimeout: (ms: number) => {
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
47
|
+
return {
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
cleanup: () => clearTimeout(timer),
|
|
34
50
|
};
|
|
35
51
|
},
|
|
52
|
+
extractToolUse: (response: { content: Array<{ type: string }> }) => {
|
|
53
|
+
return response.content.find((b: { type: string }) => b.type === 'tool_use');
|
|
54
|
+
},
|
|
55
|
+
userMessage: (text: string) => ({ role: 'user', content: [{ type: 'text', text }] }),
|
|
36
56
|
}));
|
|
37
57
|
|
|
38
58
|
mock.module('../config/loader.js', () => ({
|
|
@@ -308,25 +308,30 @@ describe('CommitEnrichmentService', () => {
|
|
|
308
308
|
});
|
|
309
309
|
|
|
310
310
|
test('job timeout triggers retry with backoff then fails after max retries', async () => {
|
|
311
|
-
// Use a very short timeout so the real git notes write times out
|
|
312
311
|
const service = new CommitEnrichmentService({
|
|
313
312
|
maxQueueSize: 10,
|
|
314
313
|
maxConcurrency: 1,
|
|
315
|
-
jobTimeoutMs:
|
|
314
|
+
jobTimeoutMs: 10, // short timeout
|
|
316
315
|
maxRetries: 2,
|
|
317
316
|
});
|
|
318
317
|
|
|
319
318
|
const commitHash = await createCommit();
|
|
319
|
+
|
|
320
|
+
// Use a slow gitService so the timeout always wins the race.
|
|
321
|
+
// A 1ms timeout against a real git write is flaky on fast CI runners.
|
|
322
|
+
const slowGitService = new WorkspaceGitService(testDir);
|
|
323
|
+
await slowGitService.ensureInitialized();
|
|
324
|
+
slowGitService.writeNote = () => new Promise<void>(() => {});
|
|
325
|
+
|
|
320
326
|
service.enqueue({
|
|
321
327
|
workspaceDir: testDir,
|
|
322
328
|
commitHash,
|
|
323
329
|
context: makeContext(),
|
|
324
|
-
gitService,
|
|
330
|
+
gitService: slowGitService,
|
|
325
331
|
});
|
|
326
332
|
|
|
327
333
|
// Wait for all retries to complete (initial + 2 retries, with backoff)
|
|
328
334
|
// Backoff: 1s after attempt 1, 2s after attempt 2 = ~3s total
|
|
329
|
-
// But since the job itself is very fast to time out, total time is dominated by backoff
|
|
330
335
|
await waitForDrain(service, 10000);
|
|
331
336
|
await service.shutdown();
|
|
332
337
|
|
|
@@ -8,6 +8,13 @@ mock.module('../util/logger.js', () => ({
|
|
|
8
8
|
getLogger: () => new Proxy({} as Record<string, unknown>, {
|
|
9
9
|
get: () => () => {},
|
|
10
10
|
}),
|
|
11
|
+
getCliLogger: () => new Proxy({} as Record<string, unknown>, {
|
|
12
|
+
get: () => () => {},
|
|
13
|
+
}),
|
|
14
|
+
isDebug: () => false,
|
|
15
|
+
truncateForLog: (value: string, maxLen = 500) => value.length > maxLen ? value.slice(0, maxLen) + '...' : value,
|
|
16
|
+
initLogger: () => {},
|
|
17
|
+
pruneOldLogFiles: () => 0,
|
|
11
18
|
}));
|
|
12
19
|
|
|
13
20
|
mock.module('../util/platform.js', () => ({
|
|
@@ -38,6 +45,7 @@ mock.module('../util/platform.js', () => ({
|
|
|
38
45
|
isMacOS: () => false,
|
|
39
46
|
isLinux: () => true,
|
|
40
47
|
isWindows: () => false,
|
|
48
|
+
readHttpToken: () => null,
|
|
41
49
|
}));
|
|
42
50
|
|
|
43
51
|
mock.module('../tools/executor.js', () => ({
|
|
@@ -74,9 +74,9 @@ describe('AssistantConfigSchema', () => {
|
|
|
74
74
|
const result = AssistantConfigSchema.parse({});
|
|
75
75
|
expect(result.provider).toBe('anthropic');
|
|
76
76
|
expect(result.model).toBe('claude-opus-4-6');
|
|
77
|
-
expect(result.maxTokens).toBe(
|
|
77
|
+
expect(result.maxTokens).toBe(16000);
|
|
78
78
|
expect(result.apiKeys).toEqual({});
|
|
79
|
-
expect(result.thinking).toEqual({ enabled: false, budgetTokens: 10000 });
|
|
79
|
+
expect(result.thinking).toEqual({ enabled: false, budgetTokens: 10000, streamThinking: false });
|
|
80
80
|
expect(result.contextWindow).toEqual({
|
|
81
81
|
enabled: true,
|
|
82
82
|
maxInputTokens: 180000,
|
|
@@ -206,6 +206,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
206
206
|
maxEdges: 40,
|
|
207
207
|
neighborScoreMultiplier: 0.7,
|
|
208
208
|
maxDepth: 3,
|
|
209
|
+
depthDecay: true,
|
|
209
210
|
});
|
|
210
211
|
});
|
|
211
212
|
|
|
@@ -681,7 +682,7 @@ describe('AssistantConfigSchema', () => {
|
|
|
681
682
|
userConsultTimeoutSeconds: 120,
|
|
682
683
|
disclosure: {
|
|
683
684
|
enabled: true,
|
|
684
|
-
text: 'At the very beginning of the call, introduce yourself as an assistant calling on behalf of the
|
|
685
|
+
text: 'At the very beginning of the call, introduce yourself as an assistant calling on behalf of the person you represent. Do not say "AI assistant".',
|
|
685
686
|
},
|
|
686
687
|
safety: {
|
|
687
688
|
denyCategories: [],
|
|
@@ -706,6 +707,11 @@ describe('AssistantConfigSchema', () => {
|
|
|
706
707
|
callerIdentity: {
|
|
707
708
|
allowPerCallOverride: true,
|
|
708
709
|
},
|
|
710
|
+
verification: {
|
|
711
|
+
enabled: false,
|
|
712
|
+
maxAttempts: 3,
|
|
713
|
+
codeLength: 6,
|
|
714
|
+
},
|
|
709
715
|
});
|
|
710
716
|
});
|
|
711
717
|
|
|
@@ -1187,8 +1193,8 @@ describe('loadConfig with schema validation', () => {
|
|
|
1187
1193
|
const config = loadConfig();
|
|
1188
1194
|
expect(config.provider).toBe('anthropic');
|
|
1189
1195
|
expect(config.model).toBe('claude-opus-4-6');
|
|
1190
|
-
expect(config.maxTokens).toBe(
|
|
1191
|
-
expect(config.thinking).toEqual({ enabled: false, budgetTokens: 10000 });
|
|
1196
|
+
expect(config.maxTokens).toBe(16000);
|
|
1197
|
+
expect(config.thinking).toEqual({ enabled: false, budgetTokens: 10000, streamThinking: false });
|
|
1192
1198
|
expect(config.contextWindow).toEqual({
|
|
1193
1199
|
enabled: true,
|
|
1194
1200
|
maxInputTokens: 180000,
|
|
@@ -1209,7 +1215,7 @@ describe('loadConfig with schema validation', () => {
|
|
|
1209
1215
|
test('falls back to default for invalid maxTokens', () => {
|
|
1210
1216
|
writeConfig({ maxTokens: -100 });
|
|
1211
1217
|
const config = loadConfig();
|
|
1212
|
-
expect(config.maxTokens).toBe(
|
|
1218
|
+
expect(config.maxTokens).toBe(16000);
|
|
1213
1219
|
});
|
|
1214
1220
|
|
|
1215
1221
|
test('falls back to defaults for invalid nested values', () => {
|
|
@@ -1234,13 +1240,13 @@ describe('loadConfig with schema validation', () => {
|
|
|
1234
1240
|
expect(config.model).toBe('gpt-4');
|
|
1235
1241
|
expect(config.thinking.enabled).toBe(true);
|
|
1236
1242
|
expect(config.thinking.budgetTokens).toBe(5000);
|
|
1237
|
-
expect(config.maxTokens).toBe(
|
|
1243
|
+
expect(config.maxTokens).toBe(16000);
|
|
1238
1244
|
});
|
|
1239
1245
|
|
|
1240
1246
|
test('handles no config file', () => {
|
|
1241
1247
|
const config = loadConfig();
|
|
1242
1248
|
expect(config.provider).toBe('anthropic');
|
|
1243
|
-
expect(config.maxTokens).toBe(
|
|
1249
|
+
expect(config.maxTokens).toBe(16000);
|
|
1244
1250
|
});
|
|
1245
1251
|
|
|
1246
1252
|
test('partial nested objects get defaults for missing fields', () => {
|
|
@@ -287,7 +287,7 @@ describe('ContextWindowManager', () => {
|
|
|
287
287
|
expect(getSummaryFromContextMessage(userMessage)).toBeNull();
|
|
288
288
|
});
|
|
289
289
|
|
|
290
|
-
test('skips compaction during cooldown
|
|
290
|
+
test('skips compaction during cooldown', async () => {
|
|
291
291
|
const provider = createProvider(() => {
|
|
292
292
|
throw new Error('summarizer should not be called while cooldown skip is active');
|
|
293
293
|
});
|
|
@@ -307,7 +307,7 @@ describe('ContextWindowManager', () => {
|
|
|
307
307
|
lastCompactedAt: Date.now() - 30_000,
|
|
308
308
|
});
|
|
309
309
|
expect(result.compacted).toBe(false);
|
|
310
|
-
expect(result.reason).toBe('compaction cooldown active
|
|
310
|
+
expect(result.reason).toBe('compaction cooldown active');
|
|
311
311
|
});
|
|
312
312
|
|
|
313
313
|
test('ignores cooldown and compacts under severe token pressure', async () => {
|
|
@@ -338,6 +338,34 @@ describe('ContextWindowManager', () => {
|
|
|
338
338
|
expect(result.reason).toBeUndefined();
|
|
339
339
|
});
|
|
340
340
|
|
|
341
|
+
test('force=true bypasses cooldown for context-too-large recovery', async () => {
|
|
342
|
+
const provider = createProvider(() => ({
|
|
343
|
+
content: [{ type: 'text', text: '## Goals\n- forced compaction' }],
|
|
344
|
+
model: 'mock-model',
|
|
345
|
+
usage: { inputTokens: 60, outputTokens: 12 },
|
|
346
|
+
stopReason: 'end_turn',
|
|
347
|
+
}));
|
|
348
|
+
const manager = new ContextWindowManager(
|
|
349
|
+
provider,
|
|
350
|
+
'system prompt',
|
|
351
|
+
makeConfig({ maxInputTokens: 260, targetInputTokens: 180, preserveRecentUserTurns: 1 }),
|
|
352
|
+
);
|
|
353
|
+
const long = 'c'.repeat(220);
|
|
354
|
+
const history: Message[] = [
|
|
355
|
+
message('user', `u1 ${long}`),
|
|
356
|
+
message('assistant', `a1 ${long}`),
|
|
357
|
+
message('user', `u2 ${long}`),
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
// Same setup as the cooldown test, but with force=true — should compact.
|
|
361
|
+
const result = await manager.maybeCompact(history, undefined, {
|
|
362
|
+
lastCompactedAt: Date.now() - 30_000,
|
|
363
|
+
force: true,
|
|
364
|
+
});
|
|
365
|
+
expect(result.compacted).toBe(true);
|
|
366
|
+
expect(result.reason).toBeUndefined();
|
|
367
|
+
});
|
|
368
|
+
|
|
341
369
|
test('image-heavy payload is no longer underestimated as below-threshold', async () => {
|
|
342
370
|
const provider = createProvider(() => ({
|
|
343
371
|
content: [{ type: 'text', text: '## Goals\n- compacted image-heavy history' }],
|