@vellumai/assistant 0.8.1 → 0.8.2
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 +2 -7
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +5 -0
- package/docker-init-apt-root.sh +94 -0
- package/docker-kata-apt-env.sh +39 -0
- package/docs/plugins.md +88 -47
- package/docs/skills.md +9 -7
- package/examples/plugins/echo/README.md +27 -27
- package/examples/plugins/echo/package.json +3 -0
- package/examples/plugins/echo/register.ts +31 -31
- package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
- package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
- package/openapi.yaml +325 -3
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +83 -10
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/sync-web-search-catalog.ts +47 -25
- package/src/__tests__/agent-image-optimize.test.ts +11 -3
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
- package/src/__tests__/anthropic-provider.test.ts +45 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
- package/src/__tests__/app-executors.test.ts +220 -4
- package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/channel-availability-routes.test.ts +206 -0
- package/src/__tests__/channel-delivery-store.test.ts +289 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
- package/src/__tests__/clawhub.test.ts +75 -16
- package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +21 -0
- package/src/__tests__/config-set-route.test.ts +80 -0
- package/src/__tests__/config-sounds-sync.test.ts +97 -0
- package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
- package/src/__tests__/context-search-conversations-source.test.ts +117 -2
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +7 -0
- package/src/__tests__/context-token-estimator.test.ts +1 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
- package/src/__tests__/conversation-agent-loop.test.ts +2 -0
- package/src/__tests__/conversation-error.test.ts +42 -3
- package/src/__tests__/conversation-fork-crud.test.ts +82 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
- package/src/__tests__/conversation-lifecycle.test.ts +173 -0
- package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +4 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
- package/src/__tests__/conversation-queue.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
- package/src/__tests__/conversation-slash-queue.test.ts +59 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
- package/src/__tests__/conversation-sync-tags.test.ts +235 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +3 -2
- package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
- package/src/__tests__/disk-pressure-tools.test.ts +1 -0
- package/src/__tests__/dm-backfill.test.ts +121 -10
- package/src/__tests__/document-tool-security.test.ts +258 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/edit-propagation.test.ts +33 -0
- package/src/__tests__/empty-response-pipeline.test.ts +0 -4
- package/src/__tests__/external-plugin-loader.test.ts +60 -36
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/helpers/tar-fixtures.ts +39 -0
- package/src/__tests__/helpers/wait-for.ts +21 -0
- package/src/__tests__/history-repair-pipeline.test.ts +0 -3
- package/src/__tests__/history-repair.test.ts +73 -0
- package/src/__tests__/host-app-control-proxy.test.ts +266 -10
- package/src/__tests__/image-credentials.test.ts +1 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
- package/src/__tests__/inference-profile-reaper.test.ts +4 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
- package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
- package/src/__tests__/injector-chain.test.ts +10 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
- package/src/__tests__/list-messages-page-latest.test.ts +55 -0
- package/src/__tests__/llm-call-pipeline.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +55 -13
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +31 -29
- package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
- package/src/__tests__/managed-store.test.ts +84 -192
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
- package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
- package/src/__tests__/oauth-commands-routes.test.ts +168 -16
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
- package/src/__tests__/openai-provider.test.ts +24 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
- package/src/__tests__/persistence-pipeline.test.ts +0 -2
- package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
- package/src/__tests__/platform.test.ts +2 -0
- package/src/__tests__/plugin-api-shim.test.ts +125 -0
- package/src/__tests__/plugin-bootstrap.test.ts +10 -36
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/plugin-registry.test.ts +0 -77
- package/src/__tests__/plugin-route-contribution.test.ts +0 -1
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
- package/src/__tests__/plugin-types.test.ts +3 -13
- package/src/__tests__/process-message-background-slack.test.ts +8 -1
- package/src/__tests__/process-message-display-content.test.ts +421 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
- package/src/__tests__/provider-error-scenarios.test.ts +111 -0
- package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
- package/src/__tests__/schedule-routes.test.ts +50 -3
- package/src/__tests__/schedule-store.test.ts +94 -0
- package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
- package/src/__tests__/schema-transforms.test.ts +20 -0
- package/src/__tests__/search-skills-unified.test.ts +0 -5
- package/src/__tests__/server-history-render.test.ts +43 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
- package/src/__tests__/skill-load-tool.test.ts +27 -89
- package/src/__tests__/skill-memory.test.ts +23 -3
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
- package/src/__tests__/skills-install-extract.test.ts +49 -38
- package/src/__tests__/skills-install-staging.test.ts +159 -0
- package/src/__tests__/skills-uninstall.test.ts +9 -41
- package/src/__tests__/skills.test.ts +51 -58
- package/src/__tests__/slack-channel-config.test.ts +9 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/system-prompt.test.ts +737 -63
- package/src/__tests__/terminal-tools.test.ts +28 -1
- package/src/__tests__/thread-backfill.test.ts +557 -27
- package/src/__tests__/title-generate-pipeline.test.ts +0 -13
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
- package/src/__tests__/tool-error-pipeline.test.ts +0 -3
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +16 -4
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
- package/src/__tests__/turn-events-store.test.ts +256 -0
- package/src/__tests__/twilio-routes.test.ts +4 -0
- package/src/__tests__/user-plugin-loader.test.ts +0 -7
- package/src/__tests__/voice-session-bridge.test.ts +198 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
- package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
- package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
- package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
- package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/types.ts +108 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/commands/__tests__/schedules.test.ts +491 -0
- package/src/cli/commands/changelog.ts +106 -42
- package/src/cli/commands/conversations.ts +102 -17
- package/src/cli/commands/default-action.ts +10 -53
- package/src/cli/commands/notifications.ts +329 -317
- package/src/cli/commands/plugins.ts +185 -0
- package/src/cli/commands/schedules.ts +391 -0
- package/src/cli/commands/telemetry.ts +40 -0
- package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
- package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
- package/src/cli/lib/cli-colors.ts +12 -0
- package/src/cli/lib/confirm-prompt.ts +79 -0
- package/src/cli/lib/install-from-github.ts +304 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +38 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
- package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
- package/src/config/bundled-skills/document/SKILL.md +23 -3
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
- package/src/config/bundled-tool-registry.ts +6 -0
- package/src/config/feature-flag-registry.json +41 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +7 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/channels.ts +8 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1047 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/context/window-manager.ts +197 -1520
- package/src/credential-execution/managed-catalog.ts +37 -0
- package/src/credential-health/credential-health-service.ts +280 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
- package/src/daemon/approval-generators.ts +8 -6
- package/src/daemon/config-watcher.ts +94 -31
- package/src/daemon/conversation-agent-loop.ts +169 -9
- package/src/daemon/conversation-error.ts +171 -37
- package/src/daemon/conversation-lifecycle.ts +53 -40
- package/src/daemon/conversation-messaging.ts +25 -6
- package/src/daemon/conversation-process.ts +49 -12
- package/src/daemon/conversation-runtime-assembly.ts +16 -1
- package/src/daemon/conversation-slash.ts +12 -5
- package/src/daemon/conversation-store.ts +11 -4
- package/src/daemon/conversation-tool-setup.ts +39 -7
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/shared.ts +14 -5
- package/src/daemon/handlers/skills.ts +111 -108
- package/src/daemon/history-repair.ts +28 -1
- package/src/daemon/host-app-control-proxy.ts +98 -23
- package/src/daemon/lifecycle.ts +45 -35
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +49 -0
- package/src/daemon/message-protocol.ts +1 -0
- package/src/daemon/message-types/conversations.ts +25 -0
- package/src/daemon/message-types/messages.ts +61 -0
- package/src/daemon/message-types/subagents.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pkb-reminder-builder.test.ts +1 -1
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +21 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +29 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +41 -0
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +22 -0
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
- package/src/mcp/client.ts +20 -4
- package/src/media/image-credentials.ts +3 -3
- package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
- package/src/memory/__tests__/conversation-queries.test.ts +263 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
- package/src/memory/__tests__/message-content.test.ts +35 -0
- package/src/memory/bookmark-crud.ts +42 -10
- package/src/memory/context-search/sources/conversations.ts +62 -2
- package/src/memory/context-search/sources/workspace.ts +4 -0
- package/src/memory/conversation-crud.ts +63 -19
- package/src/memory/conversation-queries.ts +110 -10
- package/src/memory/db-init.ts +6 -0
- package/src/memory/delivery-crud.ts +152 -5
- package/src/memory/embedding-backend.ts +4 -4
- package/src/memory/external-conversation-store.ts +66 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
- package/src/memory/graph/conversation-graph-memory.ts +31 -15
- package/src/memory/graph/tools.ts +3 -3
- package/src/memory/indexer.ts +34 -29
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
- package/src/memory/jobs/embed-concept-page.ts +20 -11
- package/src/memory/jobs-worker.ts +6 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
- package/src/memory/message-content.ts +1 -1
- package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
- package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
- package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
- package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
- package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/inference.ts +1 -3
- package/src/memory/schema/infrastructure.ts +12 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/injection.test.ts +98 -8
- package/src/memory/v2/__tests__/migration.test.ts +87 -0
- package/src/memory/v2/__tests__/page-index.test.ts +83 -0
- package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
- package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
- package/src/memory/v2/__tests__/router.test.ts +15 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
- package/src/memory/v2/injection.ts +32 -6
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +35 -5
- package/src/memory/v2/prompts/router.ts +11 -8
- package/src/memory/v2/prompts/sweep.ts +2 -2
- package/src/memory/v2/qdrant.ts +135 -7
- package/src/memory/v2/router.ts +9 -8
- package/src/memory/v2/skill-store.ts +120 -35
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
- package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
- package/src/messaging/providers/slack/adapter.ts +43 -5
- package/src/messaging/providers/slack/client.ts +27 -0
- package/src/messaging/providers/slack/deep-link.ts +65 -0
- package/src/messaging/providers/slack/download.ts +104 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
- package/src/messaging/providers/slack/message-metadata.ts +27 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
- package/src/messaging/providers/slack/render-transcript.ts +69 -5
- package/src/messaging/providers/slack/types.ts +20 -1
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/decision-engine.ts +2 -1
- package/src/notifications/emit-signal.ts +20 -1
- package/src/notifications/home-feed-side-effect.ts +54 -0
- package/src/notifications/signal.ts +3 -1
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.ts +6 -2
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/question-prompter.test.ts +416 -0
- package/src/permissions/question-prompter.ts +294 -0
- package/src/platform/client.test.ts +1 -1
- package/src/platform/client.ts +1 -1
- package/src/plugin-api/constants.ts +26 -0
- package/src/plugin-api/index.ts +34 -1
- package/src/plugin-api/types.ts +104 -22
- package/src/plugins/defaults/circuit-breaker.ts +0 -5
- package/src/plugins/defaults/compaction.ts +0 -4
- package/src/plugins/defaults/empty-response.ts +0 -2
- package/src/plugins/defaults/history-repair.ts +0 -2
- package/src/plugins/defaults/injectors.ts +36 -3
- package/src/plugins/defaults/llm-call.ts +0 -2
- package/src/plugins/defaults/memory-retrieval.ts +0 -1
- package/src/plugins/defaults/overflow-reduce.ts +0 -1
- package/src/plugins/defaults/persistence.ts +0 -2
- package/src/plugins/defaults/title-generate.ts +0 -5
- package/src/plugins/defaults/token-estimate.ts +0 -2
- package/src/plugins/defaults/tool-error.ts +0 -7
- package/src/plugins/defaults/tool-execute.ts +0 -2
- package/src/plugins/defaults/tool-result-truncate.ts +0 -4
- package/src/plugins/ensure-plugin-api-shim.ts +96 -0
- package/src/plugins/external-api.ts +104 -0
- package/src/plugins/external-plugin-loader.ts +105 -32
- package/src/plugins/feature-gate.ts +22 -0
- package/src/plugins/pipeline.ts +37 -0
- package/src/plugins/registry.ts +48 -80
- package/src/plugins/types.ts +31 -26
- package/src/plugins/user-loader.ts +21 -2
- package/src/proactive-artifact/aux-message-injector.ts +11 -0
- package/src/proactive-artifact/job.test.ts +37 -5
- package/src/prompts/__tests__/system-prompt.test.ts +12 -0
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -166
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +173 -0
- package/src/providers/__tests__/inference.test.ts +22 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/connection-resolution.ts +7 -0
- package/src/providers/inference/adapter-factory.ts +41 -4
- package/src/providers/inference/connections.ts +74 -29
- package/src/providers/inference/resolve-auth.ts +12 -4
- package/src/providers/model-catalog.ts +294 -12
- package/src/providers/openai/chat-completions-provider.ts +10 -2
- package/src/providers/openrouter/client.ts +7 -0
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
- package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
- package/src/providers/provider-availability.ts +17 -2
- package/src/providers/provider-catalog-visibility.ts +36 -0
- package/src/providers/registry.ts +22 -14
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +42 -14
- package/src/runtime/auth/route-policy.ts +8 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-types.ts +19 -0
- package/src/runtime/migrations/origin-mode.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
- package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
- package/src/runtime/routes/acp-routes-list.test.ts +143 -0
- package/src/runtime/routes/acp-routes.ts +5 -3
- package/src/runtime/routes/auth-routes.ts +1 -1
- package/src/runtime/routes/bookmark-routes.ts +5 -3
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/channel-availability-routes.ts +121 -0
- package/src/runtime/routes/conversation-cli-routes.ts +44 -3
- package/src/runtime/routes/conversation-list-routes.ts +3 -20
- package/src/runtime/routes/conversation-management-routes.ts +17 -42
- package/src/runtime/routes/conversation-query-routes.ts +40 -35
- package/src/runtime/routes/conversation-routes.ts +90 -11
- package/src/runtime/routes/documents-routes.ts +25 -86
- package/src/runtime/routes/group-routes.ts +5 -0
- package/src/runtime/routes/inbound-conversation.ts +28 -8
- package/src/runtime/routes/inbound-message-handler.ts +236 -41
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
- package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
- package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
- package/src/runtime/routes/integrations/slack/share.ts +4 -52
- package/src/runtime/routes/integrations/slack/token.ts +43 -0
- package/src/runtime/routes/integrations/twilio.ts +6 -13
- package/src/runtime/routes/notification-routes.ts +1 -1
- package/src/runtime/routes/oauth-commands-routes.ts +105 -15
- package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
- package/src/runtime/routes/question-routes.ts +259 -0
- package/src/runtime/routes/rename-conversation-routes.ts +2 -33
- package/src/runtime/routes/schedule-routes.ts +4 -7
- package/src/runtime/routes/subagents-routes.ts +57 -18
- package/src/runtime/routes/telemetry-routes.ts +27 -0
- package/src/runtime/routes/tts-routes.ts +27 -2
- package/src/runtime/routes/workspace-routes.test.ts +43 -0
- package/src/runtime/routes/workspace-routes.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +39 -7
- package/src/runtime/sync/resource-sync-events.ts +93 -1
- package/src/schedule/schedule-store.ts +27 -2
- package/src/schedule/scheduler.ts +9 -1
- package/src/security/__tests__/untrusted-content.test.ts +86 -0
- package/src/security/untrusted-content.ts +93 -8
- package/src/skills/catalog-files.ts +1 -1
- package/src/skills/catalog-install.ts +233 -116
- package/src/skills/clawhub.ts +70 -13
- package/src/skills/managed-store.ts +4 -119
- package/src/skills/skillssh-registry.ts +27 -48
- package/src/subagent/manager.ts +15 -7
- package/src/telemetry/types.ts +113 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
- package/src/telemetry/usage-telemetry-reporter.ts +113 -7
- package/src/tools/apps/executors.ts +58 -7
- package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
- package/src/tools/ask-question/ask-question-tool.ts +304 -0
- package/src/tools/browser/browser-execution.ts +15 -11
- package/src/tools/computer-use/definitions.ts +3 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/document/document-tool.ts +124 -1
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +5 -2
- package/src/tools/host-filesystem/transfer.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/registry.ts +17 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schema-transforms.ts +7 -2
- package/src/tools/side-effects.ts +1 -0
- package/src/tools/skills/delete-managed.ts +4 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/scaffold-managed.ts +3 -2
- package/src/tools/subagent/notify-parent.ts +1 -1
- package/src/tools/system/request-permission.ts +2 -2
- package/src/tools/terminal/safe-env.ts +60 -1
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/types.ts +72 -21
- package/src/tools/ui-surface/definitions.ts +6 -5
- package/src/tts/__tests__/provider-adapters.test.ts +76 -2
- package/src/tts/providers/elevenlabs-provider.ts +75 -1
- package/src/types/onboarding-context.ts +2 -0
- package/src/util/errors.ts +17 -0
- package/src/util/platform.ts +10 -0
- package/src/watcher/__tests__/engine.test.ts +22 -0
- package/src/watcher/engine.ts +6 -2
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
- package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
- package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
- package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +39 -9
- package/src/workspace/migrations/types.ts +4 -0
- package/examples/plugins/echo/bun.lock +0 -25
- package/src/__tests__/context-window-manager.test.ts +0 -2481
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for the `assistant changelog` CLI command.
|
|
3
3
|
*
|
|
4
|
+
* Every test exercises the public CLI surface via `runCli`. Internal helpers
|
|
5
|
+
* (cache plumbing, version compare, rendering) are validated through the
|
|
6
|
+
* commands that exercise them, not via module-level test exports.
|
|
7
|
+
*
|
|
4
8
|
* Coverage:
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* - End-to-end command actions (default = latest, --since, show, list,
|
|
13
|
-
* --json, missing tag, empty release list).
|
|
9
|
+
* - Default action (latest stable, --since, --json).
|
|
10
|
+
* - `show <version>` (renders, --json, 404, persists to cache).
|
|
11
|
+
* - `list` (rows, --json, --limit/--no-cache propagation).
|
|
12
|
+
* - Cache behavior (fresh cache short-circuits fetch; --no-cache bypass;
|
|
13
|
+
* stale TTL refetch; cache capped at CACHE_STABLE_LIMIT stable
|
|
14
|
+
* releases; pagination buffer absorbs drafts/prereleases).
|
|
15
|
+
* - Error mapping (rate-limit → friendly stderr; non-zero exit).
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
existsSync,
|
|
20
|
+
mkdirSync,
|
|
21
|
+
mkdtempSync,
|
|
22
|
+
readFileSync,
|
|
23
|
+
rmSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from "node:fs";
|
|
17
26
|
import { tmpdir } from "node:os";
|
|
18
27
|
import { join } from "node:path";
|
|
19
28
|
import {
|
|
@@ -98,6 +107,18 @@ const REL_DRAFT: FakeRelease = {
|
|
|
98
107
|
prerelease: false,
|
|
99
108
|
};
|
|
100
109
|
|
|
110
|
+
function stableRelease(tag: string, body = `release ${tag}`): FakeRelease {
|
|
111
|
+
return {
|
|
112
|
+
tag_name: tag,
|
|
113
|
+
name: tag,
|
|
114
|
+
body,
|
|
115
|
+
published_at: "2026-04-01T12:00:00Z",
|
|
116
|
+
html_url: `https://github.com/vellum-ai/vellum-assistant/releases/tag/${tag}`,
|
|
117
|
+
draft: false,
|
|
118
|
+
prerelease: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
101
122
|
// ── fetch mock harness ───────────────────────────────────────────────
|
|
102
123
|
|
|
103
124
|
type FetchHandler = (url: string, init?: RequestInit) => Promise<Response>;
|
|
@@ -143,25 +164,29 @@ function jsonResponse(payload: unknown, status = 200): Response {
|
|
|
143
164
|
});
|
|
144
165
|
}
|
|
145
166
|
|
|
167
|
+
const cachePath = join(TMP_ROOT, "data", "changelog-cache.json");
|
|
168
|
+
|
|
169
|
+
interface CacheShape {
|
|
170
|
+
fetchedAt: string;
|
|
171
|
+
recent: FakeRelease[];
|
|
172
|
+
byTag: Record<string, FakeRelease>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function readCacheFile(): CacheShape | null {
|
|
176
|
+
if (!existsSync(cachePath)) return null;
|
|
177
|
+
return JSON.parse(readFileSync(cachePath, "utf-8")) as CacheShape;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function writeCacheFile(cache: CacheShape): void {
|
|
181
|
+
mkdirSync(join(TMP_ROOT, "data"), { recursive: true });
|
|
182
|
+
writeFileSync(cachePath, JSON.stringify(cache, null, 2));
|
|
183
|
+
}
|
|
184
|
+
|
|
146
185
|
// ── Import module under test (after mocks) ───────────────────────────
|
|
147
186
|
|
|
148
|
-
const { registerChangelogCommand
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
normalizeTag,
|
|
152
|
-
stableReleases,
|
|
153
|
-
parseLimit,
|
|
154
|
-
renderRelease,
|
|
155
|
-
renderList,
|
|
156
|
-
readCache,
|
|
157
|
-
writeCache,
|
|
158
|
-
isStale,
|
|
159
|
-
loadReleases,
|
|
160
|
-
loadReleaseByTag,
|
|
161
|
-
getCachePath,
|
|
162
|
-
} = __testing;
|
|
163
|
-
|
|
164
|
-
// ── stdout / exit capture ────────────────────────────────────────────
|
|
187
|
+
const { registerChangelogCommand } = await import("../changelog.js");
|
|
188
|
+
|
|
189
|
+
// ── CLI driver ───────────────────────────────────────────────────────
|
|
165
190
|
|
|
166
191
|
interface CapturedRun {
|
|
167
192
|
stdout: string;
|
|
@@ -216,287 +241,32 @@ async function runCli(argv: string[]): Promise<CapturedRun> {
|
|
|
216
241
|
};
|
|
217
242
|
}
|
|
218
243
|
|
|
219
|
-
// ──
|
|
220
|
-
|
|
221
|
-
describe("changelog helpers", () => {
|
|
222
|
-
describe("compareTags", () => {
|
|
223
|
-
test("equal tags compare as 0", () => {
|
|
224
|
-
expect(compareTags("v0.8.0", "v0.8.0")).toBe(0);
|
|
225
|
-
});
|
|
226
|
-
test("higher major beats lower", () => {
|
|
227
|
-
expect(compareTags("v1.0.0", "v0.99.99")).toBeGreaterThan(0);
|
|
228
|
-
});
|
|
229
|
-
test("higher patch beats lower", () => {
|
|
230
|
-
expect(compareTags("v0.8.1", "v0.8.0")).toBeGreaterThan(0);
|
|
231
|
-
});
|
|
232
|
-
test("accepts inputs without the v prefix", () => {
|
|
233
|
-
expect(compareTags("0.8.1", "v0.8.0")).toBeGreaterThan(0);
|
|
234
|
-
expect(compareTags("0.7.0", "0.8.0")).toBeLessThan(0);
|
|
235
|
-
});
|
|
236
|
-
test("missing patch parts default to 0", () => {
|
|
237
|
-
expect(compareTags("v0.8", "v0.8.0")).toBe(0);
|
|
238
|
-
expect(compareTags("v0.8.1", "v0.8")).toBeGreaterThan(0);
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe("normalizeTag", () => {
|
|
243
|
-
test("adds v prefix when missing", () => {
|
|
244
|
-
expect(normalizeTag("0.8.0")).toBe("v0.8.0");
|
|
245
|
-
});
|
|
246
|
-
test("leaves v prefix untouched", () => {
|
|
247
|
-
expect(normalizeTag("v0.8.0")).toBe("v0.8.0");
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe("stableReleases", () => {
|
|
252
|
-
test("drops drafts and prereleases", () => {
|
|
253
|
-
const filtered = stableReleases([
|
|
254
|
-
REL_080,
|
|
255
|
-
REL_080_RC,
|
|
256
|
-
REL_DRAFT,
|
|
257
|
-
REL_079,
|
|
258
|
-
]);
|
|
259
|
-
expect(filtered.map((r) => r.tag_name)).toEqual(["v0.8.0", "v0.7.9"]);
|
|
260
|
-
});
|
|
261
|
-
test("preserves order of the stable subset", () => {
|
|
262
|
-
const filtered = stableReleases([REL_079, REL_080]);
|
|
263
|
-
expect(filtered.map((r) => r.tag_name)).toEqual(["v0.7.9", "v0.8.0"]);
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe("parseLimit", () => {
|
|
268
|
-
test("falls back when input is missing", () => {
|
|
269
|
-
expect(parseLimit(undefined, 30)).toBe(30);
|
|
270
|
-
});
|
|
271
|
-
test("falls back on garbage input", () => {
|
|
272
|
-
expect(parseLimit("not-a-number", 30)).toBe(30);
|
|
273
|
-
});
|
|
274
|
-
test("falls back when input is zero or negative", () => {
|
|
275
|
-
expect(parseLimit("0", 30)).toBe(30);
|
|
276
|
-
expect(parseLimit("-5", 30)).toBe(30);
|
|
277
|
-
});
|
|
278
|
-
test("clamps to the upper bound of 100", () => {
|
|
279
|
-
expect(parseLimit("999", 30)).toBe(100);
|
|
280
|
-
});
|
|
281
|
-
test("accepts valid values", () => {
|
|
282
|
-
expect(parseLimit("42", 30)).toBe(42);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe("renderRelease", () => {
|
|
287
|
-
test("includes heading, date, html url, and body", () => {
|
|
288
|
-
const rendered = renderRelease(REL_080);
|
|
289
|
-
expect(rendered).toContain("# v0.8.0 — Tavily");
|
|
290
|
-
expect(rendered).toContain("Published: 2026-05-10");
|
|
291
|
-
expect(rendered).toContain(REL_080.html_url);
|
|
292
|
-
expect(rendered).toContain("Tavily web search");
|
|
293
|
-
});
|
|
294
|
-
test("falls back to tag when name is empty", () => {
|
|
295
|
-
const rendered = renderRelease({ ...REL_080, name: "" });
|
|
296
|
-
expect(rendered).toContain("# v0.8.0");
|
|
297
|
-
});
|
|
298
|
-
test("placeholder when body is empty", () => {
|
|
299
|
-
const rendered = renderRelease({ ...REL_080, body: " " });
|
|
300
|
-
expect(rendered).toContain("(no release body)");
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
describe("renderList", () => {
|
|
305
|
-
test("formats one row per release", () => {
|
|
306
|
-
const rendered = renderList([REL_080, REL_079]);
|
|
307
|
-
const lines = rendered.split("\n");
|
|
308
|
-
expect(lines).toHaveLength(2);
|
|
309
|
-
expect(lines[0]).toContain("v0.8.0");
|
|
310
|
-
expect(lines[0]).toContain("2026-05-10");
|
|
311
|
-
expect(lines[1]).toContain("v0.7.9");
|
|
312
|
-
});
|
|
313
|
-
test("handles empty input", () => {
|
|
314
|
-
expect(renderList([])).toBe("No releases found.");
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// ── Cache plumbing ───────────────────────────────────────────────────
|
|
320
|
-
|
|
321
|
-
describe("changelog cache", () => {
|
|
322
|
-
test("cache path lives under the workspace data dir", () => {
|
|
323
|
-
expect(getCachePath()).toBe(join(TMP_ROOT, "data", "changelog-cache.json"));
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("readCache returns null when the file is missing", () => {
|
|
327
|
-
expect(readCache()).toBeNull();
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test("write + read roundtrip", () => {
|
|
331
|
-
const store = {
|
|
332
|
-
fetchedAt: new Date().toISOString(),
|
|
333
|
-
releases: [REL_080, REL_079],
|
|
334
|
-
};
|
|
335
|
-
writeCache(store);
|
|
336
|
-
const loaded = readCache();
|
|
337
|
-
expect(loaded).not.toBeNull();
|
|
338
|
-
expect(loaded?.releases.map((r) => r.tag_name)).toEqual([
|
|
339
|
-
"v0.8.0",
|
|
340
|
-
"v0.7.9",
|
|
341
|
-
]);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
test("readCache returns null on corrupt JSON", () => {
|
|
345
|
-
writeCache({ fetchedAt: new Date().toISOString(), releases: [REL_080] });
|
|
346
|
-
// Re-write garbage on top of the cache file.
|
|
347
|
-
writeFileSync(getCachePath(), "{not valid json");
|
|
348
|
-
expect(readCache()).toBeNull();
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
test("readCache returns null when shape is wrong", () => {
|
|
352
|
-
mkdirSync(join(TMP_ROOT, "data"), { recursive: true });
|
|
353
|
-
writeFileSync(getCachePath(), JSON.stringify({ foo: "bar" }));
|
|
354
|
-
expect(readCache()).toBeNull();
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
test("isStale flags caches older than the TTL", () => {
|
|
358
|
-
const fresh = { fetchedAt: new Date().toISOString(), releases: [] };
|
|
359
|
-
expect(isStale(fresh)).toBe(false);
|
|
360
|
-
const old = {
|
|
361
|
-
fetchedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
362
|
-
releases: [],
|
|
363
|
-
};
|
|
364
|
-
expect(isStale(old)).toBe(true);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
test("isStale treats unparseable timestamps as stale", () => {
|
|
368
|
-
expect(isStale({ fetchedAt: "not-a-date", releases: [] })).toBe(true);
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
// ── Cache-aware loaders ──────────────────────────────────────────────
|
|
373
|
-
|
|
374
|
-
describe("loadReleases", () => {
|
|
375
|
-
test("returns cached releases when fresh and big enough", async () => {
|
|
376
|
-
writeCache({
|
|
377
|
-
fetchedAt: new Date().toISOString(),
|
|
378
|
-
releases: [REL_080, REL_079],
|
|
379
|
-
});
|
|
380
|
-
fetchHandler = async () => jsonResponse([]);
|
|
381
|
-
const result = await loadReleases({ noCache: false, limit: 2 });
|
|
382
|
-
expect(result.map((r) => r.tag_name)).toEqual(["v0.8.0", "v0.7.9"]);
|
|
383
|
-
expect(fetchCalls).toHaveLength(0);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
test("fetches from GitHub when cache is missing", async () => {
|
|
387
|
-
fetchHandler = async () => jsonResponse([REL_080, REL_079]);
|
|
388
|
-
const result = await loadReleases({ noCache: false, limit: 30 });
|
|
389
|
-
expect(result.map((r) => r.tag_name)).toEqual(["v0.8.0", "v0.7.9"]);
|
|
390
|
-
expect(fetchCalls).toHaveLength(1);
|
|
391
|
-
expect(fetchCalls[0]).toContain(
|
|
392
|
-
"https://api.github.com/repos/vellum-ai/vellum-assistant/releases",
|
|
393
|
-
);
|
|
394
|
-
// Cache should now be populated.
|
|
395
|
-
expect(readCache()?.releases).toHaveLength(2);
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
test("--no-cache forces a refetch", async () => {
|
|
399
|
-
writeCache({
|
|
400
|
-
fetchedAt: new Date().toISOString(),
|
|
401
|
-
releases: [REL_080, REL_079],
|
|
402
|
-
});
|
|
403
|
-
fetchHandler = async () => jsonResponse([REL_080]);
|
|
404
|
-
const result = await loadReleases({ noCache: true, limit: 30 });
|
|
405
|
-
expect(result.map((r) => r.tag_name)).toEqual(["v0.8.0"]);
|
|
406
|
-
expect(fetchCalls).toHaveLength(1);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
test("stale cache triggers a refetch", async () => {
|
|
410
|
-
writeCache({
|
|
411
|
-
fetchedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
412
|
-
releases: [REL_079],
|
|
413
|
-
});
|
|
414
|
-
fetchHandler = async () => jsonResponse([REL_080, REL_079]);
|
|
415
|
-
const result = await loadReleases({ noCache: false, limit: 30 });
|
|
416
|
-
expect(result.map((r) => r.tag_name)).toEqual(["v0.8.0", "v0.7.9"]);
|
|
417
|
-
expect(fetchCalls).toHaveLength(1);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test("cache with fewer entries than the requested limit triggers a refetch", async () => {
|
|
421
|
-
writeCache({
|
|
422
|
-
fetchedAt: new Date().toISOString(),
|
|
423
|
-
releases: [REL_080],
|
|
424
|
-
});
|
|
425
|
-
fetchHandler = async () => jsonResponse([REL_080, REL_079]);
|
|
426
|
-
const result = await loadReleases({ noCache: false, limit: 2 });
|
|
427
|
-
expect(result).toHaveLength(2);
|
|
428
|
-
expect(fetchCalls).toHaveLength(1);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test("maps 403 to a rate-limit message", async () => {
|
|
432
|
-
fetchHandler = async () => new Response("rate limit", { status: 403 });
|
|
433
|
-
await expect(loadReleases({ noCache: true, limit: 30 })).rejects.toThrow(
|
|
434
|
-
/rate limit/i,
|
|
435
|
-
);
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
describe("loadReleaseByTag", () => {
|
|
440
|
-
test("returns from cache when the tag is present", async () => {
|
|
441
|
-
writeCache({
|
|
442
|
-
fetchedAt: new Date().toISOString(),
|
|
443
|
-
releases: [REL_080, REL_079],
|
|
444
|
-
});
|
|
445
|
-
fetchHandler = async () => jsonResponse({}, 500);
|
|
446
|
-
const found = await loadReleaseByTag("v0.8.0", { noCache: false });
|
|
447
|
-
expect(found?.tag_name).toBe("v0.8.0");
|
|
448
|
-
expect(fetchCalls).toHaveLength(0);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
test("falls through to fetch when the tag is missing from cache", async () => {
|
|
452
|
-
writeCache({
|
|
453
|
-
fetchedAt: new Date().toISOString(),
|
|
454
|
-
releases: [REL_080],
|
|
455
|
-
});
|
|
456
|
-
fetchHandler = async () => jsonResponse(REL_079);
|
|
457
|
-
const found = await loadReleaseByTag("v0.7.9", { noCache: false });
|
|
458
|
-
expect(found?.tag_name).toBe("v0.7.9");
|
|
459
|
-
expect(fetchCalls).toHaveLength(1);
|
|
460
|
-
expect(fetchCalls[0]).toContain("/tags/v0.7.9");
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
test("--no-cache always fetches", async () => {
|
|
464
|
-
writeCache({
|
|
465
|
-
fetchedAt: new Date().toISOString(),
|
|
466
|
-
releases: [REL_080],
|
|
467
|
-
});
|
|
468
|
-
fetchHandler = async () => jsonResponse(REL_080);
|
|
469
|
-
await loadReleaseByTag("v0.8.0", { noCache: true });
|
|
470
|
-
expect(fetchCalls).toHaveLength(1);
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
test("returns null on 404", async () => {
|
|
474
|
-
fetchHandler = async () => new Response("not found", { status: 404 });
|
|
475
|
-
const found = await loadReleaseByTag("v99.99.99", { noCache: true });
|
|
476
|
-
expect(found).toBeNull();
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// ── End-to-end command surface ───────────────────────────────────────
|
|
244
|
+
// ── Default action ───────────────────────────────────────────────────
|
|
481
245
|
|
|
482
|
-
describe("changelog
|
|
483
|
-
test("
|
|
246
|
+
describe("assistant changelog (default action)", () => {
|
|
247
|
+
test("shows the latest stable release, filtering drafts and prereleases", async () => {
|
|
484
248
|
fetchHandler = async () =>
|
|
485
249
|
jsonResponse([REL_DRAFT, REL_080_RC, REL_080, REL_079]);
|
|
486
250
|
const result = await runCli(["changelog"]);
|
|
487
251
|
expect(result.exitCode).toBe(0);
|
|
488
252
|
expect(result.stdout).toContain("# v0.8.0 — Tavily");
|
|
253
|
+
expect(result.stdout).toContain("Published: 2026-05-10");
|
|
254
|
+
expect(result.stdout).toContain(REL_080.html_url);
|
|
255
|
+
expect(result.stdout).toContain("Tavily web search");
|
|
489
256
|
expect(result.stdout).not.toContain("draft");
|
|
490
257
|
expect(result.stdout).not.toContain("rc.1");
|
|
491
258
|
});
|
|
492
259
|
|
|
260
|
+
test("--json emits the latest release as a JSON object", async () => {
|
|
261
|
+
fetchHandler = async () => jsonResponse([REL_080, REL_079]);
|
|
262
|
+
const result = await runCli(["changelog", "--json"]);
|
|
263
|
+
expect(result.exitCode).toBe(0);
|
|
264
|
+
const parsed = JSON.parse(result.stdout) as { tag_name: string };
|
|
265
|
+
expect(parsed.tag_name).toBe("v0.8.0");
|
|
266
|
+
});
|
|
267
|
+
|
|
493
268
|
test("--since concatenates every newer stable release, newest first", async () => {
|
|
494
|
-
const REL_081 =
|
|
495
|
-
...REL_080,
|
|
496
|
-
tag_name: "v0.8.1",
|
|
497
|
-
name: "v0.8.1",
|
|
498
|
-
body: "patch notes",
|
|
499
|
-
};
|
|
269
|
+
const REL_081 = stableRelease("v0.8.1", "patch notes");
|
|
500
270
|
fetchHandler = async () => jsonResponse([REL_081, REL_080, REL_079]);
|
|
501
271
|
const result = await runCli(["changelog", "--since", "0.7.9"]);
|
|
502
272
|
expect(result.exitCode).toBe(0);
|
|
@@ -507,43 +277,117 @@ describe("changelog command", () => {
|
|
|
507
277
|
expect(result.stdout).not.toContain("v0.7.9");
|
|
508
278
|
});
|
|
509
279
|
|
|
510
|
-
test("--since
|
|
280
|
+
test("--since accepts a tag with or without a v prefix", async () => {
|
|
511
281
|
fetchHandler = async () => jsonResponse([REL_080, REL_079]);
|
|
512
|
-
const result = await runCli(["changelog", "--since", "
|
|
282
|
+
const result = await runCli(["changelog", "--since", "v0.7.9"]);
|
|
513
283
|
expect(result.exitCode).toBe(0);
|
|
284
|
+
expect(result.stdout).toContain("v0.8.0");
|
|
514
285
|
});
|
|
515
286
|
|
|
516
|
-
test("--
|
|
287
|
+
test("--since with no newer releases exits 0 and emits no release bodies", async () => {
|
|
517
288
|
fetchHandler = async () => jsonResponse([REL_080, REL_079]);
|
|
518
|
-
const result = await runCli(["changelog", "--
|
|
289
|
+
const result = await runCli(["changelog", "--since", "1.0.0"]);
|
|
519
290
|
expect(result.exitCode).toBe(0);
|
|
520
|
-
|
|
521
|
-
|
|
291
|
+
expect(result.stdout).not.toContain("Published:");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("--since --json emits an empty releases array when nothing matches", async () => {
|
|
295
|
+
fetchHandler = async () => jsonResponse([REL_080]);
|
|
296
|
+
const result = await runCli(["changelog", "--since", "1.0.0", "--json"]);
|
|
297
|
+
expect(result.exitCode).toBe(0);
|
|
298
|
+
const parsed = JSON.parse(result.stdout) as { releases: unknown[] };
|
|
299
|
+
expect(parsed.releases).toEqual([]);
|
|
522
300
|
});
|
|
523
301
|
|
|
524
|
-
test("
|
|
302
|
+
test("empty release list (after stable filter) exits non-zero", async () => {
|
|
303
|
+
fetchHandler = async () => jsonResponse([REL_080_RC, REL_DRAFT]);
|
|
304
|
+
const result = await runCli(["changelog"]);
|
|
305
|
+
expect(result.exitCode).toBe(1);
|
|
306
|
+
expect(result.stderr).toContain("No releases found");
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// ── show <version> ───────────────────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
describe("assistant changelog show <version>", () => {
|
|
313
|
+
test("prints the named release", async () => {
|
|
525
314
|
fetchHandler = async () => jsonResponse(REL_080);
|
|
526
315
|
const result = await runCli(["changelog", "show", "0.8.0"]);
|
|
527
316
|
expect(result.exitCode).toBe(0);
|
|
528
317
|
expect(result.stdout).toContain("# v0.8.0 — Tavily");
|
|
318
|
+
expect(fetchCalls[0]).toContain("/releases/tags/v0.8.0");
|
|
529
319
|
});
|
|
530
320
|
|
|
531
|
-
test("
|
|
321
|
+
test("accepts a v-prefixed input", async () => {
|
|
322
|
+
fetchHandler = async () => jsonResponse(REL_080);
|
|
323
|
+
const result = await runCli(["changelog", "show", "v0.8.0"]);
|
|
324
|
+
expect(result.exitCode).toBe(0);
|
|
325
|
+
expect(fetchCalls[0]).toContain("/releases/tags/v0.8.0");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("--json forwards through the parent into the show subcommand", async () => {
|
|
329
|
+
fetchHandler = async () => jsonResponse(REL_080);
|
|
330
|
+
const result = await runCli(["changelog", "show", "0.8.0", "--json"]);
|
|
331
|
+
expect(result.exitCode).toBe(0);
|
|
332
|
+
const parsed = JSON.parse(result.stdout) as { tag_name: string };
|
|
333
|
+
expect(parsed.tag_name).toBe("v0.8.0");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("404 surfaces a friendly stderr and exits non-zero", async () => {
|
|
532
337
|
fetchHandler = async () => new Response("not found", { status: 404 });
|
|
533
338
|
const result = await runCli(["changelog", "show", "99.99.99"]);
|
|
534
339
|
expect(result.exitCode).toBe(1);
|
|
535
340
|
expect(result.stderr).toContain("No release found");
|
|
536
341
|
});
|
|
537
342
|
|
|
538
|
-
test("
|
|
539
|
-
|
|
343
|
+
test("persists a fetched tag into the cache so the next call short-circuits", async () => {
|
|
344
|
+
let callCount = 0;
|
|
345
|
+
fetchHandler = async () => {
|
|
346
|
+
callCount += 1;
|
|
347
|
+
return jsonResponse(REL_079);
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const first = await runCli(["changelog", "show", "0.7.9"]);
|
|
351
|
+
expect(first.exitCode).toBe(0);
|
|
352
|
+
expect(callCount).toBe(1);
|
|
353
|
+
|
|
354
|
+
const cached = readCacheFile();
|
|
355
|
+
expect(cached?.byTag["v0.7.9"]?.tag_name).toBe("v0.7.9");
|
|
356
|
+
|
|
357
|
+
const second = await runCli(["changelog", "show", "0.7.9"]);
|
|
358
|
+
expect(second.exitCode).toBe(0);
|
|
359
|
+
expect(callCount).toBe(1); // no extra fetch
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("--no-cache skips the cached entry and refetches", async () => {
|
|
363
|
+
writeCacheFile({
|
|
364
|
+
fetchedAt: new Date().toISOString(),
|
|
365
|
+
recent: [],
|
|
366
|
+
byTag: { "v0.8.0": REL_080 },
|
|
367
|
+
});
|
|
368
|
+
fetchHandler = async () => jsonResponse(REL_080);
|
|
369
|
+
const result = await runCli(["changelog", "show", "0.8.0", "--no-cache"]);
|
|
370
|
+
expect(result.exitCode).toBe(0);
|
|
371
|
+
expect(fetchCalls).toHaveLength(1);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// ── list ──────────────────────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
describe("assistant changelog list", () => {
|
|
378
|
+
test("prints one row per stable release", async () => {
|
|
379
|
+
fetchHandler = async () =>
|
|
380
|
+
jsonResponse([REL_080_RC, REL_080, REL_DRAFT, REL_079]);
|
|
540
381
|
const result = await runCli(["changelog", "list"]);
|
|
541
382
|
expect(result.exitCode).toBe(0);
|
|
542
|
-
|
|
543
|
-
expect(
|
|
383
|
+
const lines = result.stdout.trim().split("\n");
|
|
384
|
+
expect(lines).toHaveLength(2);
|
|
385
|
+
expect(lines[0]).toContain("v0.8.0");
|
|
386
|
+
expect(lines[0]).toContain("2026-05-10");
|
|
387
|
+
expect(lines[1]).toContain("v0.7.9");
|
|
544
388
|
});
|
|
545
389
|
|
|
546
|
-
test("
|
|
390
|
+
test("--json emits a releases array", async () => {
|
|
547
391
|
fetchHandler = async () => jsonResponse([REL_080, REL_079]);
|
|
548
392
|
const result = await runCli(["changelog", "list", "--json"]);
|
|
549
393
|
expect(result.exitCode).toBe(0);
|
|
@@ -556,9 +400,9 @@ describe("changelog command", () => {
|
|
|
556
400
|
]);
|
|
557
401
|
});
|
|
558
402
|
|
|
559
|
-
test("
|
|
403
|
+
test("--no-cache --json --limit forwards parent flags into the subcommand", async () => {
|
|
560
404
|
fetchHandler = async () =>
|
|
561
|
-
jsonResponse([REL_080, REL_079,
|
|
405
|
+
jsonResponse([REL_080, REL_079, stableRelease("v0.7.8")]);
|
|
562
406
|
const result = await runCli([
|
|
563
407
|
"changelog",
|
|
564
408
|
"list",
|
|
@@ -572,22 +416,163 @@ describe("changelog command", () => {
|
|
|
572
416
|
releases: Array<{ tag_name: string }>;
|
|
573
417
|
};
|
|
574
418
|
expect(parsed.releases.length).toBeGreaterThan(0);
|
|
575
|
-
|
|
419
|
+
// Even with --limit 5, the page-size buffer absorbs drafts/prereleases.
|
|
420
|
+
expect(fetchCalls[0]).toMatch(/per_page=\d+/);
|
|
576
421
|
});
|
|
422
|
+
});
|
|
577
423
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
424
|
+
// ── Cache behavior ───────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
describe("assistant changelog cache", () => {
|
|
427
|
+
test("second invocation reuses a fresh cache without re-fetching", async () => {
|
|
428
|
+
let callCount = 0;
|
|
429
|
+
fetchHandler = async () => {
|
|
430
|
+
callCount += 1;
|
|
431
|
+
return jsonResponse([REL_080, REL_079]);
|
|
432
|
+
};
|
|
433
|
+
await runCli(["changelog"]);
|
|
434
|
+
expect(callCount).toBe(1);
|
|
435
|
+
|
|
436
|
+
await runCli(["changelog"]);
|
|
437
|
+
// Second call should hit the rolling-recent cache: latest is still
|
|
438
|
+
// populated and not stale, and we only need 1 entry for the default
|
|
439
|
+
// action.
|
|
440
|
+
expect(callCount).toBe(1);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("--no-cache forces a refetch even with a fresh cache", async () => {
|
|
444
|
+
writeCacheFile({
|
|
445
|
+
fetchedAt: new Date().toISOString(),
|
|
446
|
+
recent: [REL_080, REL_079],
|
|
447
|
+
byTag: { "v0.8.0": REL_080, "v0.7.9": REL_079 },
|
|
448
|
+
});
|
|
449
|
+
let callCount = 0;
|
|
450
|
+
fetchHandler = async () => {
|
|
451
|
+
callCount += 1;
|
|
452
|
+
return jsonResponse([REL_080, REL_079]);
|
|
453
|
+
};
|
|
454
|
+
const result = await runCli(["changelog", "--no-cache"]);
|
|
581
455
|
expect(result.exitCode).toBe(0);
|
|
582
|
-
|
|
583
|
-
expect(parsed.tag_name).toBe("v0.8.0");
|
|
456
|
+
expect(callCount).toBe(1);
|
|
584
457
|
});
|
|
585
458
|
|
|
586
|
-
test("
|
|
459
|
+
test("stale cache (older than TTL) triggers a refetch", async () => {
|
|
460
|
+
writeCacheFile({
|
|
461
|
+
fetchedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
|
462
|
+
recent: [REL_079],
|
|
463
|
+
byTag: { "v0.7.9": REL_079 },
|
|
464
|
+
});
|
|
465
|
+
let callCount = 0;
|
|
466
|
+
fetchHandler = async () => {
|
|
467
|
+
callCount += 1;
|
|
468
|
+
return jsonResponse([REL_080, REL_079]);
|
|
469
|
+
};
|
|
470
|
+
await runCli(["changelog"]);
|
|
471
|
+
expect(callCount).toBe(1);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test("corrupt cache JSON is treated as a miss", async () => {
|
|
475
|
+
mkdirSync(join(TMP_ROOT, "data"), { recursive: true });
|
|
476
|
+
writeFileSync(cachePath, "{not valid json");
|
|
477
|
+
let callCount = 0;
|
|
478
|
+
fetchHandler = async () => {
|
|
479
|
+
callCount += 1;
|
|
480
|
+
return jsonResponse([REL_080]);
|
|
481
|
+
};
|
|
482
|
+
await runCli(["changelog"]);
|
|
483
|
+
expect(callCount).toBe(1);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("rolling cache is capped at 5 stable releases regardless of fetched count", async () => {
|
|
487
|
+
const stables = [
|
|
488
|
+
stableRelease("v0.9.0"),
|
|
489
|
+
stableRelease("v0.8.9"),
|
|
490
|
+
stableRelease("v0.8.8"),
|
|
491
|
+
stableRelease("v0.8.7"),
|
|
492
|
+
stableRelease("v0.8.6"),
|
|
493
|
+
stableRelease("v0.8.5"),
|
|
494
|
+
stableRelease("v0.8.4"),
|
|
495
|
+
stableRelease("v0.8.3"),
|
|
496
|
+
];
|
|
497
|
+
fetchHandler = async () => jsonResponse(stables);
|
|
498
|
+
await runCli(["changelog", "list", "--limit", "8"]);
|
|
499
|
+
|
|
500
|
+
const cached = readCacheFile();
|
|
501
|
+
expect(cached?.recent.length).toBe(5);
|
|
502
|
+
expect(cached?.recent.map((r) => r.tag_name)).toEqual([
|
|
503
|
+
"v0.9.0",
|
|
504
|
+
"v0.8.9",
|
|
505
|
+
"v0.8.8",
|
|
506
|
+
"v0.8.7",
|
|
507
|
+
"v0.8.6",
|
|
508
|
+
]);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test("page-size buffer absorbs drafts/prereleases so a small --limit still surfaces stable releases", async () => {
|
|
512
|
+
// First page is mostly noise — drafts and prereleases — followed by
|
|
513
|
+
// stable releases. With a hardcoded per_page=limit the small budget
|
|
514
|
+
// would be eaten by noise. The buffer keeps stable releases visible.
|
|
515
|
+
const noise = Array.from({ length: 10 }, (_, i) => ({
|
|
516
|
+
...REL_DRAFT,
|
|
517
|
+
tag_name: `v9.9.${i}`,
|
|
518
|
+
}));
|
|
519
|
+
fetchHandler = async () => jsonResponse([...noise, REL_080, REL_079]);
|
|
520
|
+
const result = await runCli([
|
|
521
|
+
"changelog",
|
|
522
|
+
"list",
|
|
523
|
+
"--limit",
|
|
524
|
+
"2",
|
|
525
|
+
"--json",
|
|
526
|
+
]);
|
|
527
|
+
expect(result.exitCode).toBe(0);
|
|
528
|
+
const parsed = JSON.parse(result.stdout) as {
|
|
529
|
+
releases: Array<{ tag_name: string }>;
|
|
530
|
+
};
|
|
531
|
+
expect(parsed.releases.map((r) => r.tag_name)).toEqual([
|
|
532
|
+
"v0.8.0",
|
|
533
|
+
"v0.7.9",
|
|
534
|
+
]);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test("--limit larger than cached count refetches to satisfy the request", async () => {
|
|
538
|
+
writeCacheFile({
|
|
539
|
+
fetchedAt: new Date().toISOString(),
|
|
540
|
+
recent: [REL_080],
|
|
541
|
+
byTag: { "v0.8.0": REL_080 },
|
|
542
|
+
});
|
|
543
|
+
let callCount = 0;
|
|
544
|
+
fetchHandler = async () => {
|
|
545
|
+
callCount += 1;
|
|
546
|
+
return jsonResponse([REL_080, REL_079]);
|
|
547
|
+
};
|
|
548
|
+
await runCli(["changelog", "list", "--limit", "10"]);
|
|
549
|
+
expect(callCount).toBe(1);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// ── Errors ───────────────────────────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
describe("assistant changelog errors", () => {
|
|
556
|
+
test("403 rate-limit response surfaces a friendly stderr", async () => {
|
|
587
557
|
fetchHandler = async () =>
|
|
588
558
|
new Response("rate limit exceeded", { status: 403 });
|
|
589
559
|
const result = await runCli(["changelog", "--no-cache"]);
|
|
590
560
|
expect(result.exitCode).toBe(1);
|
|
591
561
|
expect(result.stderr).toMatch(/rate limit/i);
|
|
592
562
|
});
|
|
563
|
+
|
|
564
|
+
test("429 rate-limit response also surfaces the friendly message", async () => {
|
|
565
|
+
fetchHandler = async () =>
|
|
566
|
+
new Response("too many requests", { status: 429 });
|
|
567
|
+
const result = await runCli(["changelog", "--no-cache"]);
|
|
568
|
+
expect(result.exitCode).toBe(1);
|
|
569
|
+
expect(result.stderr).toMatch(/rate limit/i);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test("500 from GitHub surfaces the status code", async () => {
|
|
573
|
+
fetchHandler = async () => new Response("server is sad", { status: 500 });
|
|
574
|
+
const result = await runCli(["changelog", "--no-cache"]);
|
|
575
|
+
expect(result.exitCode).toBe(1);
|
|
576
|
+
expect(result.stderr).toContain("500");
|
|
577
|
+
});
|
|
593
578
|
});
|