@vellumai/assistant 0.4.56 → 0.4.57
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/ARCHITECTURE.md +10 -10
- package/Dockerfile +3 -0
- package/README.md +11 -11
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/memory.md +3 -4
- package/docs/credential-execution-service.md +13 -20
- package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +7 -7
- package/src/__tests__/anthropic-provider.test.ts +172 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
- package/src/__tests__/approval-cascade.test.ts +2 -2
- package/src/__tests__/approval-routes-http.test.ts +3 -4
- package/src/__tests__/asset-materialize-tool.test.ts +5 -5
- package/src/__tests__/asset-search-tool.test.ts +1 -1
- package/src/__tests__/assistant-attachments.test.ts +5 -5
- package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
- package/src/__tests__/attachments-store.test.ts +2 -2
- package/src/__tests__/avatar-e2e.test.ts +5 -3
- package/src/__tests__/browser-skill-endstate.test.ts +0 -1
- package/src/__tests__/call-routes-http.test.ts +2 -2
- package/src/__tests__/callback-handoff-copy.test.ts +1 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +0 -1
- package/src/__tests__/checker.test.ts +31 -32
- package/src/__tests__/chrome-cdp.test.ts +47 -18
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +9 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop.test.ts +11 -4
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +33 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
- package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
- package/src/__tests__/conversation-queue.test.ts +23 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +185 -173
- package/src/__tests__/conversation-seed-composer.test.ts +1 -1
- package/src/__tests__/conversation-slash-queue.test.ts +4 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
- package/src/__tests__/conversation-starter-routes.test.ts +291 -0
- package/src/__tests__/conversation-wipe.test.ts +438 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
- package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
- package/src/__tests__/credential-security-e2e.test.ts +20 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +227 -0
- package/src/__tests__/credentials-cli.test.ts +3 -0
- package/src/__tests__/date-context.test.ts +59 -377
- package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
- package/src/__tests__/encrypted-store.test.ts +237 -15
- package/src/__tests__/ephemeral-permissions.test.ts +4 -5
- package/src/__tests__/event-bus.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +4 -4
- package/src/__tests__/gemini-provider.test.ts +6 -9
- package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/host-shell-tool.test.ts +6 -6
- package/src/__tests__/http-user-message-parity.test.ts +2 -2
- package/src/__tests__/intent-routing.test.ts +51 -99
- package/src/__tests__/invite-routes-http.test.ts +5 -0
- package/src/__tests__/list-messages-attachments.test.ts +1 -1
- package/src/__tests__/managed-proxy-context.test.ts +2 -5
- package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
- package/src/__tests__/media-generate-image.test.ts +32 -15
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
- package/src/__tests__/memory-recall-quality.test.ts +4 -3
- package/src/__tests__/memory-regressions.test.ts +86 -90
- package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
- package/src/__tests__/migration-export-http.test.ts +26 -27
- package/src/__tests__/migration-import-commit-http.test.ts +165 -37
- package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
- package/src/__tests__/migration-validate-http.test.ts +16 -16
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
- package/src/__tests__/notification-broadcaster.test.ts +1 -1
- package/src/__tests__/notification-decision-fallback.test.ts +2 -2
- package/src/__tests__/notification-decision-identity.test.ts +8 -9
- package/src/__tests__/notification-decision-strategy.test.ts +1 -1
- package/src/__tests__/notification-deep-link.test.ts +1 -1
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
- package/src/__tests__/oauth-store.test.ts +1 -3
- package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
- package/src/__tests__/onboarding-template-contract.test.ts +23 -59
- package/src/__tests__/provider-error-scenarios.test.ts +154 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
- package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
- package/src/__tests__/provider-registry-ollama.test.ts +5 -2
- package/src/__tests__/qdrant-manager.test.ts +7 -7
- package/src/__tests__/ratelimit.test.ts +0 -74
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/scheduler-recurrence.test.ts +46 -2
- package/src/__tests__/schema-transforms.test.ts +114 -54
- package/src/__tests__/secret-onetime-send.test.ts +20 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
- package/src/__tests__/secret-scanner-executor.test.ts +1 -2
- package/src/__tests__/send-endpoint-busy.test.ts +63 -4
- package/src/__tests__/send-notification-tool.test.ts +2 -2
- package/src/__tests__/shell-credential-ref.test.ts +0 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
- package/src/__tests__/skill-memory.test.ts +547 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
- package/src/__tests__/slack-channel-config.test.ts +109 -94
- package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
- package/src/__tests__/swarm-recursion.test.ts +2 -2
- package/src/__tests__/swarm-tool.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +19 -66
- package/src/__tests__/telegram-config.test.ts +121 -0
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +1 -1
- package/src/__tests__/trace-emitter.test.ts +8 -1
- package/src/__tests__/trust-store.test.ts +7 -8
- package/src/__tests__/twilio-routes.test.ts +1 -18
- package/src/__tests__/user-reference.test.ts +82 -2
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +3 -3
- package/src/avatar/ascii-renderer.ts +2 -2
- package/src/avatar/png-renderer.ts +2 -2
- package/src/avatar/resvg-lazy.ts +21 -0
- package/src/calls/guardian-dispatch.ts +1 -1
- package/src/calls/relay-access-wait.ts +2 -2
- package/src/calls/twilio-rest.ts +0 -248
- package/src/cli/AGENTS.md +5 -8
- package/src/cli/__tests__/notifications.test.ts +5 -5
- package/src/cli/commands/avatar.ts +64 -2
- package/src/cli/commands/conversations.ts +131 -1
- package/src/cli/commands/credentials.ts +2 -0
- package/src/cli/commands/notifications.ts +3 -3
- package/src/cli.ts +10 -0
- package/src/config/bundled-skills/acp/SKILL.md +5 -5
- package/src/config/bundled-skills/acp/TOOLS.json +6 -6
- package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
- package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
- package/src/config/bundled-skills/browser/SKILL.md +15 -15
- package/src/config/bundled-skills/browser/TOOLS.json +14 -14
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
- package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
- package/src/config/bundled-skills/contacts/SKILL.md +3 -3
- package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
- package/src/config/bundled-skills/document/SKILL.md +4 -4
- package/src/config/bundled-skills/document/TOOLS.json +2 -2
- package/src/config/bundled-skills/followups/TOOLS.json +3 -3
- package/src/config/bundled-skills/gmail/SKILL.md +32 -32
- package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
- package/src/config/bundled-skills/google-calendar/types.ts +1 -1
- package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
- package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
- package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
- package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +29 -25
- package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/notifications/SKILL.md +3 -3
- package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
- package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
- package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
- package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
- package/src/config/bundled-skills/schedule/SKILL.md +26 -26
- package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
- package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
- package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
- package/src/config/bundled-skills/sequences/SKILL.md +2 -2
- package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
- package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
- package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
- package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
- package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
- package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
- package/src/config/bundled-skills/slack/SKILL.md +2 -2
- package/src/config/bundled-skills/slack/TOOLS.json +8 -8
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
- package/src/config/bundled-skills/tasks/SKILL.md +1 -1
- package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
- package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
- package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
- package/src/config/bundled-skills/watcher/SKILL.md +4 -4
- package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
- package/src/config/feature-flag-registry.json +33 -17
- package/src/config/schemas/sandbox.ts +1 -1
- package/src/config/schemas/services.ts +13 -3
- package/src/config/schemas/timeouts.ts +0 -10
- package/src/contacts/contact-store.ts +63 -0
- package/src/contacts/contacts-write.ts +1 -1
- package/src/daemon/assistant-attachments.ts +2 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
- package/src/daemon/conversation-agent-loop.ts +7 -30
- package/src/daemon/conversation-error.ts +24 -0
- package/src/daemon/conversation-memory.ts +8 -7
- package/src/daemon/conversation-runtime-assembly.ts +139 -274
- package/src/daemon/conversation-slash.ts +7 -26
- package/src/daemon/conversation-surfaces.ts +14 -0
- package/src/daemon/conversation-tool-setup.ts +9 -8
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +10 -83
- package/src/daemon/handlers/config-channels.ts +12 -2
- package/src/daemon/handlers/config-slack-channel.ts +7 -1
- package/src/daemon/handlers/config-telegram.ts +6 -1
- package/src/daemon/handlers/conversations.ts +2 -2
- package/src/daemon/handlers/skills.ts +4 -0
- package/src/daemon/lifecycle.ts +28 -4
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +1 -5
- package/src/daemon/shutdown-handlers.ts +9 -3
- package/src/daemon/tool-side-effects.ts +40 -0
- package/src/daemon/trace-emitter.ts +25 -2
- package/src/events/domain-events.ts +1 -1
- package/src/events/tool-permission-telemetry-listener.ts +46 -0
- package/src/inbound/platform-callback-registration.ts +0 -18
- package/src/media/app-icon-generator.ts +15 -8
- package/src/media/avatar-router.ts +15 -8
- package/src/media/gemini-image-service.ts +125 -21
- package/src/memory/attachments-store.ts +3 -3
- package/src/memory/channel-verification-sessions.ts +6 -6
- package/src/memory/conversation-crud.ts +196 -1
- package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
- package/src/memory/conversation-title-service.ts +2 -3
- package/src/memory/db-init.ts +25 -1
- package/src/memory/invite-store.ts +4 -4
- package/src/memory/items-extractor.ts +4 -4
- package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/jobs-worker.ts +7 -5
- package/src/memory/lifecycle-events-store.ts +63 -0
- package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
- package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
- package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
- package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
- package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
- package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +223 -96
- package/src/memory/retriever.ts +115 -138
- package/src/memory/schema/calls.ts +1 -1
- package/src/memory/schema/contacts.ts +1 -1
- package/src/memory/schema/infrastructure.ts +29 -0
- package/src/memory/schema/memory-core.ts +7 -17
- package/src/memory/schema/notifications.ts +1 -1
- package/src/memory/search/formatting.ts +23 -6
- package/src/memory/search/lexical.ts +2 -0
- package/src/memory/search/semantic.ts +2 -0
- package/src/memory/search/staleness.ts +1 -0
- package/src/memory/search/types.ts +4 -0
- package/src/memory/task-memory-cleanup.ts +96 -6
- package/src/memory/trace-event-store.ts +148 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/decision-engine.ts +2 -2
- package/src/notifications/emit-signal.ts +4 -4
- package/src/notifications/events-store.ts +4 -4
- package/src/notifications/signal.ts +1 -1
- package/src/oauth/manual-token-connection.ts +49 -25
- package/src/permissions/checker.ts +6 -5
- package/src/permissions/defaults.ts +4 -4
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
- package/src/prompts/cache-boundary.ts +8 -0
- package/src/prompts/system-prompt.ts +105 -634
- package/src/prompts/templates/BOOTSTRAP.md +166 -33
- package/src/prompts/templates/IDENTITY.md +8 -23
- package/src/prompts/templates/SOUL.md +20 -41
- package/src/prompts/templates/USER.md +3 -19
- package/src/prompts/user-reference.ts +14 -16
- package/src/providers/anthropic/client.ts +46 -2
- package/src/providers/gemini/client.ts +6 -9
- package/src/providers/managed-proxy/constants.ts +1 -7
- package/src/providers/managed-proxy/context.ts +0 -1
- package/src/providers/model-intents.ts +5 -5
- package/src/providers/openai/client.ts +10 -1
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/ratelimit.ts +0 -35
- package/src/providers/registry.ts +3 -5
- package/src/providers/retry.ts +18 -1
- package/src/runtime/access-request-helper.ts +1 -1
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/channel-verification-service.ts +1 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/guardian-vellum-migration.ts +63 -1
- package/src/runtime/http-server.ts +8 -4
- package/src/runtime/migrations/vbundle-builder.ts +212 -32
- package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
- package/src/runtime/migrations/vbundle-importer.ts +66 -1
- package/src/runtime/migrations/vbundle-validator.ts +17 -3
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
- package/src/runtime/routes/attachment-routes.ts +2 -2
- package/src/runtime/routes/btw-routes.ts +9 -0
- package/src/runtime/routes/channel-verification-routes.ts +19 -2
- package/src/runtime/routes/conversation-management-routes.ts +55 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +49 -5
- package/src/runtime/routes/conversation-starter-routes.ts +207 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +25 -13
- package/src/runtime/routes/secret-routes.ts +18 -0
- package/src/runtime/routes/settings-routes.ts +8 -8
- package/src/runtime/routes/telemetry-routes.ts +53 -0
- package/src/runtime/routes/trace-event-routes.ts +62 -0
- package/src/runtime/tool-grant-request-helper.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +47 -31
- package/src/security/encrypted-store.ts +263 -78
- package/src/skills/catalog-install.ts +10 -0
- package/src/skills/managed-store.ts +2 -0
- package/src/skills/skill-memory.ts +220 -0
- package/src/subagent/manager.ts +1 -4
- package/src/telemetry/types.ts +10 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
- package/src/telemetry/usage-telemetry-reporter.ts +51 -4
- package/src/tools/AGENTS.md +11 -11
- package/src/tools/acp/spawn.ts +1 -1
- package/src/tools/apps/executors.ts +8 -8
- package/src/tools/apps/registry.ts +1 -1
- package/src/tools/assets/materialize.ts +6 -6
- package/src/tools/assets/search.ts +10 -10
- package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
- package/src/tools/browser/auth-detector.ts +6 -6
- package/src/tools/browser/browser-execution.ts +13 -13
- package/src/tools/browser/browser-manager.ts +3 -3
- package/src/tools/browser/chrome-cdp.ts +5 -5
- package/src/tools/browser/jit-auth.ts +2 -2
- package/src/tools/browser/network-recorder.test.ts +2 -2
- package/src/tools/browser/network-recorder.ts +3 -3
- package/src/tools/browser/runtime-check.ts +3 -3
- package/src/tools/claude-code/claude-code.ts +2 -2
- package/src/tools/computer-use/definitions.ts +18 -18
- package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
- package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
- package/src/tools/credentials/broker-types.ts +5 -5
- package/src/tools/credentials/broker.ts +15 -15
- package/src/tools/credentials/metadata-store.ts +2 -2
- package/src/tools/credentials/resolve.ts +1 -1
- package/src/tools/credentials/selection.ts +1 -1
- package/src/tools/credentials/tool-policy.ts +1 -1
- package/src/tools/credentials/vault.ts +115 -25
- package/src/tools/execution-target.ts +2 -2
- package/src/tools/executor.ts +7 -7
- package/src/tools/filesystem/edit.ts +2 -2
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +1 -1
- package/src/tools/host-filesystem/edit.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -1
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +9 -8
- package/src/tools/mcp/mcp-tool-factory.ts +7 -6
- package/src/tools/memory/definitions.ts +6 -5
- package/src/tools/memory/handlers.test.ts +1 -1
- package/src/tools/network/__tests__/web-search.test.ts +3 -3
- package/src/tools/network/domain-normalize.ts +2 -2
- package/src/tools/network/script-proxy/session-manager.ts +10 -10
- package/src/tools/network/web-fetch.ts +1 -1
- package/src/tools/network/web-search.ts +3 -3
- package/src/tools/permission-checker.ts +8 -8
- package/src/tools/registry.ts +7 -7
- package/src/tools/schedule/list.ts +2 -2
- package/src/tools/schema-transforms.ts +31 -21
- package/src/tools/secret-detection-handler.ts +1 -1
- package/src/tools/sensitive-output-placeholders.ts +1 -1
- package/src/tools/shared/filesystem/edit-engine.ts +1 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
- package/src/tools/shared/filesystem/image-read.ts +25 -5
- package/src/tools/shared/filesystem/path-policy.ts +2 -2
- package/src/tools/shared/shell-output.ts +1 -1
- package/src/tools/side-effects.ts +1 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +3 -3
- package/src/tools/skills/sandbox-runner.ts +3 -3
- package/src/tools/subagent/read.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -2
- package/src/tools/swarm/delegate.ts +3 -3
- package/src/tools/system/request-permission.ts +5 -4
- package/src/tools/terminal/backends/native.ts +4 -4
- package/src/tools/terminal/parser.ts +6 -6
- package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
- package/src/tools/terminal/shell.ts +16 -16
- package/src/tools/tool-approval-handler.ts +21 -12
- package/src/tools/tool-manifest.ts +4 -4
- package/src/tools/types.ts +3 -3
- package/src/tools/ui-surface/definitions.ts +9 -37
- package/src/tools/watcher/list.ts +1 -1
- package/src/util/logger.ts +7 -2
- package/src/util/retry.ts +29 -1
- package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
- package/src/cli/reference.ts +0 -38
- package/src/memory/job-handlers/capability-cards.ts +0 -420
- package/src/runtime/routes/thread-starter-routes.ts +0 -294
|
@@ -49,6 +49,11 @@ mock.module("../util/retry.js", () => {
|
|
|
49
49
|
return status === 429 || status >= 500;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
const RETRYABLE_NETWORK_MESSAGE_PATTERNS = [
|
|
53
|
+
/socket.*closed unexpectedly/i,
|
|
54
|
+
/socket hang up/i,
|
|
55
|
+
];
|
|
56
|
+
|
|
52
57
|
function isRetryableNetworkError(error: unknown): boolean {
|
|
53
58
|
if (!(error instanceof Error)) return false;
|
|
54
59
|
const retryableCodes = new Set([
|
|
@@ -63,6 +68,18 @@ mock.module("../util/retry.js", () => {
|
|
|
63
68
|
const causeCode = (error.cause as NodeJS.ErrnoException).code;
|
|
64
69
|
if (causeCode && retryableCodes.has(causeCode)) return true;
|
|
65
70
|
}
|
|
71
|
+
if (
|
|
72
|
+
RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))
|
|
73
|
+
) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const cause = error.cause;
|
|
77
|
+
if (
|
|
78
|
+
cause instanceof Error &&
|
|
79
|
+
RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(cause.message))
|
|
80
|
+
) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
66
83
|
return false;
|
|
67
84
|
}
|
|
68
85
|
|
|
@@ -396,6 +413,34 @@ describe("RetryProvider — network error retries", () => {
|
|
|
396
413
|
expect(result.content[0]).toMatchObject({ type: "text", text: "ok" });
|
|
397
414
|
});
|
|
398
415
|
|
|
416
|
+
test("retries on Bun 'socket connection was closed unexpectedly' (ProviderError wrapping)", async () => {
|
|
417
|
+
const inner = makeFlaky(
|
|
418
|
+
1,
|
|
419
|
+
new ProviderError(
|
|
420
|
+
"Anthropic request failed: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()",
|
|
421
|
+
"anthropic",
|
|
422
|
+
),
|
|
423
|
+
);
|
|
424
|
+
const provider = new RetryProvider(inner);
|
|
425
|
+
|
|
426
|
+
const result = await provider.sendMessage(MESSAGES);
|
|
427
|
+
expect(inner.calls).toBe(2);
|
|
428
|
+
expect(result.stopReason).toBe("end_turn");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("retries on 'socket connection was closed unexpectedly' in error cause", async () => {
|
|
432
|
+
const cause = new Error(
|
|
433
|
+
"The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()",
|
|
434
|
+
);
|
|
435
|
+
const outer = new Error("fetch failed", { cause });
|
|
436
|
+
const inner = makeFlaky(1, outer);
|
|
437
|
+
const provider = new RetryProvider(inner);
|
|
438
|
+
|
|
439
|
+
const result = await provider.sendMessage(MESSAGES);
|
|
440
|
+
expect(inner.calls).toBe(2);
|
|
441
|
+
expect(result.content[0]).toMatchObject({ type: "text", text: "ok" });
|
|
442
|
+
});
|
|
443
|
+
|
|
399
444
|
test("does not retry on non-retryable errors", async () => {
|
|
400
445
|
const inner = makeFailing(new Error("unexpected error"));
|
|
401
446
|
const provider = new RetryProvider(inner);
|
|
@@ -419,6 +464,115 @@ describe("RetryProvider — network error retries", () => {
|
|
|
419
464
|
});
|
|
420
465
|
});
|
|
421
466
|
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
// RetryProvider — streaming corruption retries
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
describe("RetryProvider — streaming corruption retries", () => {
|
|
472
|
+
test("retries on 'Unexpected event order' (message_start before message_stop)", async () => {
|
|
473
|
+
const inner = makeFlaky(
|
|
474
|
+
1,
|
|
475
|
+
new ProviderError(
|
|
476
|
+
'Anthropic request failed: Unexpected event order, got message_start before receiving "message_stop"',
|
|
477
|
+
"anthropic",
|
|
478
|
+
),
|
|
479
|
+
);
|
|
480
|
+
const provider = new RetryProvider(inner);
|
|
481
|
+
|
|
482
|
+
const result = await provider.sendMessage(MESSAGES);
|
|
483
|
+
expect(result.stopReason).toBe("end_turn");
|
|
484
|
+
expect(inner.calls).toBe(2);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("retries on 'Unexpected event order' (event before message_start)", async () => {
|
|
488
|
+
const inner = makeFlaky(
|
|
489
|
+
1,
|
|
490
|
+
new ProviderError(
|
|
491
|
+
'Anthropic request failed: Unexpected event order, got content_block_start before "message_start"',
|
|
492
|
+
"anthropic",
|
|
493
|
+
),
|
|
494
|
+
);
|
|
495
|
+
const provider = new RetryProvider(inner);
|
|
496
|
+
|
|
497
|
+
const result = await provider.sendMessage(MESSAGES);
|
|
498
|
+
expect(inner.calls).toBe(2);
|
|
499
|
+
expect(result.model).toBe("test-model");
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("retries on 'stream ended without producing'", async () => {
|
|
503
|
+
const inner = makeFlaky(
|
|
504
|
+
1,
|
|
505
|
+
new ProviderError(
|
|
506
|
+
"Anthropic request failed: stream ended without producing a Message with role=assistant",
|
|
507
|
+
"anthropic",
|
|
508
|
+
),
|
|
509
|
+
);
|
|
510
|
+
const provider = new RetryProvider(inner);
|
|
511
|
+
|
|
512
|
+
const result = await provider.sendMessage(MESSAGES);
|
|
513
|
+
expect(inner.calls).toBe(2);
|
|
514
|
+
expect(result.content).toHaveLength(1);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test("retries on 'request ended without sending any chunks'", async () => {
|
|
518
|
+
const inner = makeFlaky(
|
|
519
|
+
1,
|
|
520
|
+
new ProviderError(
|
|
521
|
+
"Anthropic request failed: request ended without sending any chunks",
|
|
522
|
+
"anthropic",
|
|
523
|
+
),
|
|
524
|
+
);
|
|
525
|
+
const provider = new RetryProvider(inner);
|
|
526
|
+
|
|
527
|
+
await provider.sendMessage(MESSAGES);
|
|
528
|
+
expect(inner.calls).toBe(2);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("throws after exhausting retries on persistent stream corruption", async () => {
|
|
532
|
+
const inner = makeFailing(
|
|
533
|
+
new ProviderError(
|
|
534
|
+
'Anthropic request failed: Unexpected event order, got message_start before receiving "message_stop"',
|
|
535
|
+
"anthropic",
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
const provider = new RetryProvider(inner);
|
|
539
|
+
|
|
540
|
+
await expect(provider.sendMessage(MESSAGES)).rejects.toThrow(
|
|
541
|
+
"Unexpected event order",
|
|
542
|
+
);
|
|
543
|
+
expect(inner.calls).toBe(DEFAULT_MAX_RETRIES + 1);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("does not retry non-stream ProviderError without status code", async () => {
|
|
547
|
+
const inner = makeFailing(
|
|
548
|
+
new ProviderError("model not found", "anthropic"),
|
|
549
|
+
);
|
|
550
|
+
const provider = new RetryProvider(inner);
|
|
551
|
+
|
|
552
|
+
await expect(provider.sendMessage(MESSAGES)).rejects.toThrow(
|
|
553
|
+
"model not found",
|
|
554
|
+
);
|
|
555
|
+
expect(inner.calls).toBe(1);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test("does not treat stream pattern as retryable when ProviderError has a status code", async () => {
|
|
559
|
+
// A 400 error that happens to contain "Unexpected event order" should NOT be retried
|
|
560
|
+
const inner = makeFailing(
|
|
561
|
+
new ProviderError(
|
|
562
|
+
"Unexpected event order in request payload",
|
|
563
|
+
"anthropic",
|
|
564
|
+
400,
|
|
565
|
+
),
|
|
566
|
+
);
|
|
567
|
+
const provider = new RetryProvider(inner);
|
|
568
|
+
|
|
569
|
+
await expect(provider.sendMessage(MESSAGES)).rejects.toThrow(
|
|
570
|
+
"Unexpected event order",
|
|
571
|
+
);
|
|
572
|
+
expect(inner.calls).toBe(1);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
422
576
|
// ---------------------------------------------------------------------------
|
|
423
577
|
// RetryProvider — streaming + options passthrough
|
|
424
578
|
// ---------------------------------------------------------------------------
|
|
@@ -52,9 +52,9 @@ function makeProvidersConfig(provider: string, model: string): ProvidersConfig {
|
|
|
52
52
|
"image-generation": {
|
|
53
53
|
mode: "your-own",
|
|
54
54
|
provider: "gemini",
|
|
55
|
-
model: "gemini-
|
|
55
|
+
model: "gemini-3.1-flash-image-preview",
|
|
56
56
|
},
|
|
57
|
-
"web-search": { mode: "your-own", provider: "
|
|
57
|
+
"web-search": { mode: "your-own", provider: "inference-provider-native" },
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
60
|
}
|
|
@@ -69,9 +69,9 @@ function makeProvidersConfig(provider: string, model: string): ProvidersConfig {
|
|
|
69
69
|
"image-generation": {
|
|
70
70
|
mode: "your-own",
|
|
71
71
|
provider: "gemini",
|
|
72
|
-
model: "gemini-
|
|
72
|
+
model: "gemini-3.1-flash-image-preview",
|
|
73
73
|
},
|
|
74
|
-
"web-search": { mode: "your-own", provider: "
|
|
74
|
+
"web-search": { mode: "your-own", provider: "inference-provider-native" },
|
|
75
75
|
},
|
|
76
76
|
};
|
|
77
77
|
}
|
|
@@ -206,7 +206,7 @@ describe("managed proxy integration — credential precedence", () => {
|
|
|
206
206
|
expect(baseURL).toContain("/v1/runtime-proxy/anthropic");
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
test("managed gemini uses
|
|
209
|
+
test("managed gemini uses gemini proxy path", async () => {
|
|
210
210
|
enableManagedProxy();
|
|
211
211
|
mockProviderKeys = {};
|
|
212
212
|
await initializeProviders(makeProvidersConfig("anthropic", "test-model"));
|
|
@@ -216,8 +216,7 @@ describe("managed proxy integration — credential precedence", () => {
|
|
|
216
216
|
| { baseUrl?: string }
|
|
217
217
|
| undefined;
|
|
218
218
|
expect(httpOptions).toBeDefined();
|
|
219
|
-
expect(httpOptions!.baseUrl).toContain("/v1/runtime-proxy/
|
|
220
|
-
expect(httpOptions!.baseUrl).not.toContain("/v1/runtime-proxy/gemini");
|
|
219
|
+
expect(httpOptions!.baseUrl).toContain("/v1/runtime-proxy/gemini");
|
|
221
220
|
});
|
|
222
221
|
});
|
|
223
222
|
|
|
@@ -309,8 +308,8 @@ describe("managed proxy integration — ollama exclusion", () => {
|
|
|
309
308
|
});
|
|
310
309
|
|
|
311
310
|
describe("managed proxy integration — constants integrity", () => {
|
|
312
|
-
test("anthropic
|
|
313
|
-
for (const provider of ["anthropic", "gemini"
|
|
311
|
+
test("anthropic and gemini have metadata with managed=true and a proxyPath", () => {
|
|
312
|
+
for (const provider of ["anthropic", "gemini"]) {
|
|
314
313
|
const meta = MANAGED_PROVIDER_META[provider];
|
|
315
314
|
expect(meta).toBeDefined();
|
|
316
315
|
expect(meta.managed).toBe(true);
|
|
@@ -325,9 +324,9 @@ describe("managed proxy integration — constants integrity", () => {
|
|
|
325
324
|
);
|
|
326
325
|
});
|
|
327
326
|
|
|
328
|
-
test("gemini routes through
|
|
327
|
+
test("gemini routes through gemini proxy path", () => {
|
|
329
328
|
expect(MANAGED_PROVIDER_META.gemini.proxyPath).toBe(
|
|
330
|
-
"/v1/runtime-proxy/
|
|
329
|
+
"/v1/runtime-proxy/gemini",
|
|
331
330
|
);
|
|
332
331
|
});
|
|
333
332
|
|
|
@@ -25,9 +25,12 @@ describe("provider registry (ollama)", () => {
|
|
|
25
25
|
"image-generation": {
|
|
26
26
|
mode: "your-own",
|
|
27
27
|
provider: "gemini",
|
|
28
|
-
model: "gemini-
|
|
28
|
+
model: "gemini-3.1-flash-image-preview",
|
|
29
|
+
},
|
|
30
|
+
"web-search": {
|
|
31
|
+
mode: "your-own",
|
|
32
|
+
provider: "inference-provider-native",
|
|
29
33
|
},
|
|
30
|
-
"web-search": { mode: "your-own", provider: "anthropic-native" },
|
|
31
34
|
},
|
|
32
35
|
});
|
|
33
36
|
|
|
@@ -36,9 +36,9 @@ import { QdrantManager } from "../memory/qdrant-manager.js";
|
|
|
36
36
|
|
|
37
37
|
/** Short timeouts so tests complete fast but with enough headroom for CI. */
|
|
38
38
|
const FAST_TIMEOUTS = {
|
|
39
|
-
readyzPollIntervalMs:
|
|
40
|
-
readyzTimeoutMs:
|
|
41
|
-
shutdownGraceMs:
|
|
39
|
+
readyzPollIntervalMs: 5,
|
|
40
|
+
readyzTimeoutMs: 100,
|
|
41
|
+
shutdownGraceMs: 50,
|
|
42
42
|
} as const;
|
|
43
43
|
|
|
44
44
|
function placeFakeBinary(script: string): string {
|
|
@@ -248,7 +248,7 @@ describe("QdrantManager", () => {
|
|
|
248
248
|
const startPromise = mgr.start();
|
|
249
249
|
|
|
250
250
|
// Wait for spawn to happen
|
|
251
|
-
await Bun.sleep(
|
|
251
|
+
await Bun.sleep(50);
|
|
252
252
|
|
|
253
253
|
// PID file should be written
|
|
254
254
|
expect(existsSync(pidPath)).toBe(true);
|
|
@@ -277,7 +277,7 @@ describe("QdrantManager", () => {
|
|
|
277
277
|
});
|
|
278
278
|
|
|
279
279
|
const startPromise = mgr.start();
|
|
280
|
-
await Bun.sleep(
|
|
280
|
+
await Bun.sleep(50);
|
|
281
281
|
|
|
282
282
|
expect(existsSync(pidPath)).toBe(true);
|
|
283
283
|
|
|
@@ -285,8 +285,8 @@ describe("QdrantManager", () => {
|
|
|
285
285
|
await mgr.stop();
|
|
286
286
|
const stopElapsed = Date.now() - stopStart;
|
|
287
287
|
|
|
288
|
-
// Grace period is
|
|
289
|
-
expect(stopElapsed).toBeGreaterThanOrEqual(
|
|
288
|
+
// Grace period is 50ms with FAST_TIMEOUTS — should wait at least that long
|
|
289
|
+
expect(stopElapsed).toBeGreaterThanOrEqual(30);
|
|
290
290
|
expect(existsSync(pidPath)).toBe(false);
|
|
291
291
|
|
|
292
292
|
await expect(startPromise).rejects.toThrow("did not become ready");
|
|
@@ -38,7 +38,6 @@ describe("RateLimitProvider", () => {
|
|
|
38
38
|
test("allows requests under the limit", async () => {
|
|
39
39
|
const config: RateLimitConfig = {
|
|
40
40
|
maxRequestsPerMinute: 5,
|
|
41
|
-
maxTokensPerSession: 0,
|
|
42
41
|
};
|
|
43
42
|
const provider = new RateLimitProvider(makeProvider(), config);
|
|
44
43
|
|
|
@@ -50,7 +49,6 @@ describe("RateLimitProvider", () => {
|
|
|
50
49
|
test("throws RateLimitError when exceeding request limit", async () => {
|
|
51
50
|
const config: RateLimitConfig = {
|
|
52
51
|
maxRequestsPerMinute: 2,
|
|
53
|
-
maxTokensPerSession: 0,
|
|
54
52
|
};
|
|
55
53
|
const provider = new RateLimitProvider(makeProvider(), config);
|
|
56
54
|
|
|
@@ -63,7 +61,6 @@ describe("RateLimitProvider", () => {
|
|
|
63
61
|
test("unlimited when maxRequestsPerMinute is 0", async () => {
|
|
64
62
|
const config: RateLimitConfig = {
|
|
65
63
|
maxRequestsPerMinute: 0,
|
|
66
|
-
maxTokensPerSession: 0,
|
|
67
64
|
};
|
|
68
65
|
const provider = new RateLimitProvider(makeProvider(), config);
|
|
69
66
|
|
|
@@ -73,56 +70,10 @@ describe("RateLimitProvider", () => {
|
|
|
73
70
|
});
|
|
74
71
|
});
|
|
75
72
|
|
|
76
|
-
describe("session token limiting", () => {
|
|
77
|
-
test("allows requests under the token budget", async () => {
|
|
78
|
-
const config: RateLimitConfig = {
|
|
79
|
-
maxRequestsPerMinute: 0,
|
|
80
|
-
maxTokensPerSession: 1000,
|
|
81
|
-
};
|
|
82
|
-
const provider = new RateLimitProvider(makeProvider(), config);
|
|
83
|
-
|
|
84
|
-
// Each call uses 150 tokens (100 input + 50 output)
|
|
85
|
-
for (let i = 0; i < 6; i++) {
|
|
86
|
-
await provider.sendMessage(messages);
|
|
87
|
-
}
|
|
88
|
-
// 6 * 150 = 900, still under 1000
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("throws RateLimitError when token budget exhausted", async () => {
|
|
92
|
-
const config: RateLimitConfig = {
|
|
93
|
-
maxRequestsPerMinute: 0,
|
|
94
|
-
maxTokensPerSession: 300,
|
|
95
|
-
};
|
|
96
|
-
const provider = new RateLimitProvider(makeProvider(), config);
|
|
97
|
-
|
|
98
|
-
// 150 tokens per call
|
|
99
|
-
await provider.sendMessage(messages); // 150
|
|
100
|
-
await provider.sendMessage(messages); // 300
|
|
101
|
-
|
|
102
|
-
expect(provider.sendMessage(messages)).rejects.toThrow(RateLimitError);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("unlimited when maxTokensPerSession is 0", async () => {
|
|
106
|
-
const config: RateLimitConfig = {
|
|
107
|
-
maxRequestsPerMinute: 0,
|
|
108
|
-
maxTokensPerSession: 0,
|
|
109
|
-
};
|
|
110
|
-
const provider = new RateLimitProvider(
|
|
111
|
-
makeProvider({ usage: { inputTokens: 10000, outputTokens: 10000 } }),
|
|
112
|
-
config,
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
for (let i = 0; i < 10; i++) {
|
|
116
|
-
await provider.sendMessage(messages);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
73
|
describe("passthrough behavior", () => {
|
|
122
74
|
test("delegates to inner provider", async () => {
|
|
123
75
|
const config: RateLimitConfig = {
|
|
124
76
|
maxRequestsPerMinute: 0,
|
|
125
|
-
maxTokensPerSession: 0,
|
|
126
77
|
};
|
|
127
78
|
const inner = makeProvider({ model: "custom-model" });
|
|
128
79
|
const provider = new RateLimitProvider(inner, config);
|
|
@@ -134,7 +85,6 @@ describe("RateLimitProvider", () => {
|
|
|
134
85
|
test("preserves provider name", () => {
|
|
135
86
|
const config: RateLimitConfig = {
|
|
136
87
|
maxRequestsPerMinute: 0,
|
|
137
|
-
maxTokensPerSession: 0,
|
|
138
88
|
};
|
|
139
89
|
const provider = new RateLimitProvider(makeProvider(), config);
|
|
140
90
|
expect(provider.name).toBe("mock");
|
|
@@ -143,7 +93,6 @@ describe("RateLimitProvider", () => {
|
|
|
143
93
|
test("passes through all arguments to inner provider", async () => {
|
|
144
94
|
const config: RateLimitConfig = {
|
|
145
95
|
maxRequestsPerMinute: 0,
|
|
146
|
-
maxTokensPerSession: 0,
|
|
147
96
|
};
|
|
148
97
|
let receivedArgs: unknown[] = [];
|
|
149
98
|
const inner: Provider = {
|
|
@@ -173,27 +122,10 @@ describe("RateLimitProvider", () => {
|
|
|
173
122
|
});
|
|
174
123
|
});
|
|
175
124
|
|
|
176
|
-
describe("combined limits", () => {
|
|
177
|
-
test("enforces both limits simultaneously", async () => {
|
|
178
|
-
const config: RateLimitConfig = {
|
|
179
|
-
maxRequestsPerMinute: 10,
|
|
180
|
-
maxTokensPerSession: 300,
|
|
181
|
-
};
|
|
182
|
-
const provider = new RateLimitProvider(makeProvider(), config);
|
|
183
|
-
|
|
184
|
-
// Token limit should hit first (2 * 150 = 300)
|
|
185
|
-
await provider.sendMessage(messages);
|
|
186
|
-
await provider.sendMessage(messages);
|
|
187
|
-
|
|
188
|
-
expect(provider.sendMessage(messages)).rejects.toThrow(RateLimitError);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
125
|
describe("shared request timestamps", () => {
|
|
193
126
|
test("multiple providers sharing timestamps enforce a global rate limit", async () => {
|
|
194
127
|
const config: RateLimitConfig = {
|
|
195
128
|
maxRequestsPerMinute: 2,
|
|
196
|
-
maxTokensPerSession: 0,
|
|
197
129
|
};
|
|
198
130
|
const shared: number[] = [];
|
|
199
131
|
const provider1 = new RateLimitProvider(makeProvider(), config, shared);
|
|
@@ -215,7 +147,6 @@ describe("RateLimitProvider", () => {
|
|
|
215
147
|
test("out-of-order timestamps are pruned correctly (clock skew)", async () => {
|
|
216
148
|
const config: RateLimitConfig = {
|
|
217
149
|
maxRequestsPerMinute: 3,
|
|
218
|
-
maxTokensPerSession: 0,
|
|
219
150
|
};
|
|
220
151
|
const shared: number[] = [];
|
|
221
152
|
const provider = new RateLimitProvider(makeProvider(), config, shared);
|
|
@@ -236,7 +167,6 @@ describe("RateLimitProvider", () => {
|
|
|
236
167
|
test("waitSec uses actual oldest timestamp under clock skew", async () => {
|
|
237
168
|
const config: RateLimitConfig = {
|
|
238
169
|
maxRequestsPerMinute: 2,
|
|
239
|
-
maxTokensPerSession: 0,
|
|
240
170
|
};
|
|
241
171
|
const shared: number[] = [];
|
|
242
172
|
const provider = new RateLimitProvider(makeProvider(), config, shared);
|
|
@@ -266,7 +196,6 @@ describe("RateLimitProvider", () => {
|
|
|
266
196
|
const highLimit = 200_000;
|
|
267
197
|
const config: RateLimitConfig = {
|
|
268
198
|
maxRequestsPerMinute: highLimit,
|
|
269
|
-
maxTokensPerSession: 0,
|
|
270
199
|
};
|
|
271
200
|
const shared: number[] = [];
|
|
272
201
|
const provider = new RateLimitProvider(makeProvider(), config, shared);
|
|
@@ -286,7 +215,6 @@ describe("RateLimitProvider", () => {
|
|
|
286
215
|
test("shared array reference survives pruning", async () => {
|
|
287
216
|
const config: RateLimitConfig = {
|
|
288
217
|
maxRequestsPerMinute: 100,
|
|
289
|
-
maxTokensPerSession: 0,
|
|
290
218
|
};
|
|
291
219
|
const shared: number[] = [];
|
|
292
220
|
const provider1 = new RateLimitProvider(makeProvider(), config, shared);
|
|
@@ -311,7 +239,6 @@ describe("RateLimitProvider", () => {
|
|
|
311
239
|
test("concurrent calls are rate-limited because timestamp is recorded before await", async () => {
|
|
312
240
|
const config: RateLimitConfig = {
|
|
313
241
|
maxRequestsPerMinute: 1,
|
|
314
|
-
maxTokensPerSession: 0,
|
|
315
242
|
};
|
|
316
243
|
// Slow provider that yields to the event loop
|
|
317
244
|
const inner: Provider = {
|
|
@@ -343,7 +270,6 @@ describe("RateLimitProvider", () => {
|
|
|
343
270
|
test("failed inner calls still count toward request rate", async () => {
|
|
344
271
|
const config: RateLimitConfig = {
|
|
345
272
|
maxRequestsPerMinute: 1,
|
|
346
|
-
maxTokensPerSession: 0,
|
|
347
273
|
};
|
|
348
274
|
const inner: Provider = {
|
|
349
275
|
name: "failing",
|
|
@@ -24,7 +24,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
24
24
|
daemon: { standaloneRecording: true },
|
|
25
25
|
provider: "mock-provider",
|
|
26
26
|
permissions: { mode: "workspace" },
|
|
27
|
-
sandbox: { enabled: false },
|
|
28
27
|
timeouts: { toolExecutionTimeoutSec: 30, permissionTimeoutSec: 5 },
|
|
29
28
|
skills: { load: { extraDirs: [] } },
|
|
30
29
|
secretDetection: { enabled: false, allowOneTimeSend: false },
|
|
@@ -50,7 +50,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
50
50
|
model: "test",
|
|
51
51
|
provider: "test",
|
|
52
52
|
memory: { enabled: false },
|
|
53
|
-
rateLimit: { maxRequestsPerMinute: 0
|
|
53
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
54
54
|
secretDetection: { enabled: false },
|
|
55
55
|
}),
|
|
56
56
|
}));
|
|
@@ -42,7 +42,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
42
42
|
model: "test",
|
|
43
43
|
provider: "test",
|
|
44
44
|
memory: { enabled: false },
|
|
45
|
-
rateLimit: { maxRequestsPerMinute: 0
|
|
45
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
46
46
|
secretDetection: { enabled: false },
|
|
47
47
|
}),
|
|
48
48
|
}));
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
afterAll,
|
|
6
|
+
beforeAll,
|
|
7
|
+
beforeEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
mock,
|
|
11
|
+
test,
|
|
12
|
+
} from "bun:test";
|
|
5
13
|
|
|
6
14
|
const testDir = mkdtempSync(join(tmpdir(), "scheduler-recurrence-test-"));
|
|
7
15
|
|
|
@@ -86,7 +94,30 @@ function buildEndedRrule(): string {
|
|
|
86
94
|
|
|
87
95
|
// ── RRULE schedule fires through the scheduler ──────────────────────
|
|
88
96
|
|
|
97
|
+
// Replace setTimeout with a zero-delay version so the 500ms scheduler
|
|
98
|
+
// wait calls fire instantly instead of waiting real time.
|
|
99
|
+
let origSetTimeout: typeof globalThis.setTimeout;
|
|
100
|
+
|
|
89
101
|
describe("scheduler RRULE execution", () => {
|
|
102
|
+
beforeAll(() => {
|
|
103
|
+
origSetTimeout = globalThis.setTimeout;
|
|
104
|
+
globalThis.setTimeout = ((
|
|
105
|
+
fn: TimerHandler,
|
|
106
|
+
_ms?: number,
|
|
107
|
+
...args: unknown[]
|
|
108
|
+
) => {
|
|
109
|
+
// Use a small real delay so fire-and-forget async ticks have time to
|
|
110
|
+
// settle, while still cutting the 500ms waits down dramatically.
|
|
111
|
+
// 200ms gives headroom for the run_task path which does a dynamic
|
|
112
|
+
// import of task-runner.js on first invocation.
|
|
113
|
+
return origSetTimeout(fn, 200, ...args);
|
|
114
|
+
}) as typeof setTimeout;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
afterAll(() => {
|
|
118
|
+
globalThis.setTimeout = origSetTimeout;
|
|
119
|
+
});
|
|
120
|
+
|
|
90
121
|
beforeEach(() => {
|
|
91
122
|
const db = getDb();
|
|
92
123
|
db.run("DELETE FROM cron_runs");
|
|
@@ -157,8 +188,11 @@ describe("scheduler RRULE execution", () => {
|
|
|
157
188
|
forceScheduleDue(schedule.id);
|
|
158
189
|
|
|
159
190
|
const directCalls: { conversationId: string; message: string }[] = [];
|
|
191
|
+
let onMessage: (() => void) | undefined;
|
|
192
|
+
const messageReceived = new Promise<void>((r) => (onMessage = r));
|
|
160
193
|
const processMessage = async (conversationId: string, message: string) => {
|
|
161
194
|
directCalls.push({ conversationId, message });
|
|
195
|
+
onMessage?.();
|
|
162
196
|
};
|
|
163
197
|
|
|
164
198
|
const scheduler = startScheduler(
|
|
@@ -166,7 +200,17 @@ describe("scheduler RRULE execution", () => {
|
|
|
166
200
|
() => {},
|
|
167
201
|
() => {},
|
|
168
202
|
);
|
|
169
|
-
|
|
203
|
+
// The run_task path involves a dynamic import which can take >50ms in CI,
|
|
204
|
+
// exceeding the patched setTimeout delay. Await the actual callback instead
|
|
205
|
+
// of relying on a fixed timeout.
|
|
206
|
+
await Promise.race([
|
|
207
|
+
messageReceived,
|
|
208
|
+
new Promise((r) => origSetTimeout(r, 2000)),
|
|
209
|
+
]);
|
|
210
|
+
// Yield to the macrotask queue so all pending microtasks settle —
|
|
211
|
+
// the scheduler tick still needs to create the schedule run after
|
|
212
|
+
// processMessage returns.
|
|
213
|
+
await new Promise((r) => origSetTimeout(r, 0));
|
|
170
214
|
scheduler.stop();
|
|
171
215
|
|
|
172
216
|
// runTask renders the template, so processMessage gets the template text
|