@vellumai/assistant 0.8.1 → 0.8.3
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 +13 -19
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +17 -0
- package/docker-init-apt-root.sh +167 -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 +642 -5
- 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-loop-exit-reason.test.ts +272 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- 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 +147 -0
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- 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 +31 -65
- 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 +59 -1
- 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-media-retry.test.ts +19 -8
- 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 +102 -13
- 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__/date-context.test.ts +45 -0
- 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 +151 -55
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- 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 +507 -10
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- 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-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +15 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -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-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +58 -13
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-resolver.test.ts +255 -2
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +41 -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__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -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 +242 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- 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} +7 -2
- 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 +158 -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} +33 -31
- 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__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
- 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 +670 -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-087-memory-router-balanced-profile.test.ts +228 -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/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/agent/loop.ts +167 -18
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +122 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +960 -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 +388 -346
- package/src/cli/commands/plugins.ts +252 -0
- package/src/cli/commands/schedules.ts +683 -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__/search-plugins.test.ts +261 -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 +303 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +52 -2
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
- 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/phone-calls/SKILL.md +1 -1
- 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/call-site-defaults.ts +105 -0
- package/src/config/feature-flag-registry.json +41 -9
- package/src/config/llm-resolver.ts +52 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +9 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
- package/src/config/schemas/channels.ts +17 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +23 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +4 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1107 -0
- package/src/context/token-estimator.ts +34 -36
- 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 +33 -18
- 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-handlers.ts +78 -0
- package/src/daemon/conversation-agent-loop.ts +198 -11
- 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 +25 -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 -8
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +289 -0
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/conversations.ts +1 -0
- 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 +153 -27
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/lifecycle.ts +89 -91
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +85 -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/notifications.ts +21 -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 +11 -54
- package/src/daemon/pkb-reminder-builder.ts +5 -20
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +24 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +33 -0
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +73 -189
- package/src/home/__tests__/feed-types.test.ts +80 -0
- package/src/home/feed-types.ts +36 -2
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/ipc/cli-client.ts +147 -45
- 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 +483 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
- 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 +197 -11
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +12 -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 +150 -12
- package/src/memory/graph/conversation-graph-memory.ts +49 -21
- package/src/memory/graph/tools.ts +9 -40
- package/src/memory/indexer.ts +34 -29
- package/src/memory/invite-store.ts +53 -0
- 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 +24 -12
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-enqueue.ts +1 -20
- package/src/memory/memory-retrospective-job.ts +33 -6
- 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/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/index.ts +9 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +3 -3
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection.test.ts +288 -11
- 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/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection.ts +81 -26
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +63 -8
- 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/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +11 -2
- 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/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +113 -45
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +21 -1
- package/src/notifications/home-feed-side-effect.ts +138 -5
- package/src/notifications/signal.ts +3 -5
- package/src/notifications/types.ts +8 -0
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +19 -6
- 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 +74 -22
- 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 +187 -42
- 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 +40 -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 +10 -43
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -174
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +164 -0
- package/src/providers/__tests__/inference.test.ts +24 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +68 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +32 -6
- package/src/providers/inference/auth.ts +12 -0
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/connections.ts +159 -34
- package/src/providers/inference/resolve-auth.ts +14 -4
- package/src/providers/model-catalog.ts +249 -12
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +169 -8
- package/src/providers/openrouter/client.ts +49 -4
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
- 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 +38 -0
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +52 -15
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +103 -15
- package/src/runtime/auth/route-policy.ts +21 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +19 -47
- 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__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- 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 +126 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -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 +99 -35
- package/src/runtime/routes/conversation-routes.ts +97 -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 +8 -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 +199 -22
- package/src/runtime/routes/integrations/a2a.ts +235 -0
- 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/llm-call-sites-routes.ts +11 -1
- 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 +98 -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 +17 -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/memory/register.ts +1 -9
- 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 +107 -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/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/registry.ts +10 -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/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
} from "../embedding-backend.js";
|
|
37
37
|
import { invalidatePageIndex } from "./page-index.js";
|
|
38
38
|
import {
|
|
39
|
+
backfillKindOnPointsWithPrefix,
|
|
39
40
|
pruneSlugsWithPrefixExcept,
|
|
40
41
|
upsertConceptPageEmbedding,
|
|
41
42
|
} from "./qdrant.js";
|
|
@@ -78,6 +79,11 @@ export function skillSlugFor(id: string): string {
|
|
|
78
79
|
* successful re-seed so callers always see a consistent snapshot.
|
|
79
80
|
*/
|
|
80
81
|
let entries: Map<string, SkillEntry> | null = null;
|
|
82
|
+
let requestedSeedGeneration = 0;
|
|
83
|
+
let processedSeedGeneration = 0;
|
|
84
|
+
let activeSeedDrain: Promise<void> | null = null;
|
|
85
|
+
let lastSeedError: unknown = null;
|
|
86
|
+
const seedWaiters: Array<{ generation: number; resolve: () => void }> = [];
|
|
81
87
|
|
|
82
88
|
/**
|
|
83
89
|
* Seed (or re-seed) skill embeddings into the unified concept-page collection.
|
|
@@ -85,32 +91,19 @@ let entries: Map<string, SkillEntry> | null = null;
|
|
|
85
91
|
* background callers like daemon startup; pass `{ throwOnError: true }` from
|
|
86
92
|
* synchronous CLI-driven paths that need to surface failures to the operator.
|
|
87
93
|
*
|
|
88
|
-
* Single-flight + coalesced: at most one seed runs at a time
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* the
|
|
94
|
+
* Single-flight + coalesced: at most one seed runs at a time. Requests made
|
|
95
|
+
* while a seed is in flight advance the requested generation; stale in-flight
|
|
96
|
+
* snapshots are skipped before they write embeddings or replace the cache,
|
|
97
|
+
* then the drain loop immediately processes the latest generation. Strict
|
|
98
|
+
* callers observe the awaited generation's latest outcome via `lastSeedError`.
|
|
93
99
|
*/
|
|
94
|
-
let seedTail: Promise<void> = Promise.resolve();
|
|
95
|
-
let seedPending: Promise<void> | null = null;
|
|
96
|
-
let lastSeedError: unknown = null;
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await runSeedOnce();
|
|
105
|
-
});
|
|
106
|
-
seedTail = next.catch(() => {});
|
|
107
|
-
seedPending = next;
|
|
108
|
-
}
|
|
109
|
-
await seedPending;
|
|
110
|
-
if (opts.throwOnError && lastSeedError) {
|
|
111
|
-
throw lastSeedError;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
101
|
+
/**
|
|
102
|
+
* In-process latch for the legacy `kind` backfill (see
|
|
103
|
+
* {@link backfillKindOnPointsWithPrefix}). New upserts always write `kind`,
|
|
104
|
+
* so once the latch is set there is no follow-up work to do this process.
|
|
105
|
+
*/
|
|
106
|
+
let legacyKindBackfillDone = false;
|
|
114
107
|
|
|
115
108
|
/**
|
|
116
109
|
* Steps (per run):
|
|
@@ -132,7 +125,49 @@ export async function seedV2SkillEntries(
|
|
|
132
125
|
* stale points from prior catalog state (e.g. uninstalled skills).
|
|
133
126
|
* 7. Replace the module-level `entries` cache with the freshly built map.
|
|
134
127
|
*/
|
|
135
|
-
async function
|
|
128
|
+
export async function seedV2SkillEntries(
|
|
129
|
+
opts: { throwOnError?: boolean } = {},
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
const generation = ++requestedSeedGeneration;
|
|
132
|
+
const waiter = new Promise<void>((resolve) => {
|
|
133
|
+
seedWaiters.push({ generation, resolve });
|
|
134
|
+
});
|
|
135
|
+
startSeedDrainIfNeeded();
|
|
136
|
+
await waiter;
|
|
137
|
+
if (opts.throwOnError && lastSeedError) {
|
|
138
|
+
throw lastSeedError;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function startSeedDrainIfNeeded(): void {
|
|
143
|
+
if (activeSeedDrain) return;
|
|
144
|
+
if (processedSeedGeneration >= requestedSeedGeneration) return;
|
|
145
|
+
|
|
146
|
+
activeSeedDrain = drainSeedQueue().finally(() => {
|
|
147
|
+
activeSeedDrain = null;
|
|
148
|
+
startSeedDrainIfNeeded();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function drainSeedQueue(): Promise<void> {
|
|
153
|
+
while (processedSeedGeneration < requestedSeedGeneration) {
|
|
154
|
+
const generationToProcess = requestedSeedGeneration;
|
|
155
|
+
await runSeedV2SkillEntries(generationToProcess);
|
|
156
|
+
processedSeedGeneration = generationToProcess;
|
|
157
|
+
resolveSeedWaiters();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveSeedWaiters(): void {
|
|
162
|
+
for (let i = seedWaiters.length - 1; i >= 0; i -= 1) {
|
|
163
|
+
const waiter = seedWaiters[i]!;
|
|
164
|
+
if (waiter.generation > processedSeedGeneration) continue;
|
|
165
|
+
seedWaiters.splice(i, 1);
|
|
166
|
+
waiter.resolve();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function runSeedV2SkillEntries(generation: number): Promise<void> {
|
|
136
171
|
try {
|
|
137
172
|
const config = getConfig();
|
|
138
173
|
const catalog = loadSkillCatalog();
|
|
@@ -160,11 +195,19 @@ async function runSeedOnce(): Promise<void> {
|
|
|
160
195
|
// Seed uninstalled catalog skills so their activation hints are
|
|
161
196
|
// discoverable by intent. Track whether the catalog was available so we
|
|
162
197
|
// can guard pruning below.
|
|
198
|
+
//
|
|
199
|
+
// Build the legacy-backfill allowlist in parallel: every locally
|
|
200
|
+
// installed skill id (regardless of enabled state) plus every remote
|
|
201
|
+
// catalog id. Restricting the backfill to this set keeps user-authored
|
|
202
|
+
// concept pages that happen to live under `skills/<slug>` from being
|
|
203
|
+
// mis-tagged and then pruned. See `backfillKindOnPointsWithPrefix`.
|
|
204
|
+
const knownSkillIds = new Set<string>(installedIds);
|
|
163
205
|
let catalogAvailable = false;
|
|
164
206
|
try {
|
|
165
207
|
const fullCatalog = await getCatalog();
|
|
166
208
|
catalogAvailable = fullCatalog.length > 0;
|
|
167
209
|
for (const entry of fullCatalog) {
|
|
210
|
+
knownSkillIds.add(entry.id);
|
|
168
211
|
if (installedIds.has(entry.id)) continue;
|
|
169
212
|
const flagKey = entry.metadata?.vellum?.["feature-flag"];
|
|
170
213
|
if (flagKey && !isAssistantFeatureFlagEnabled(flagKey, config))
|
|
@@ -184,12 +227,16 @@ async function runSeedOnce(): Promise<void> {
|
|
|
184
227
|
// unavailable embedding backend in the all-disabled case, so pruning and
|
|
185
228
|
// cache replacement still run and clear stale state.
|
|
186
229
|
const nextEntries = new Map<string, SkillEntry>();
|
|
230
|
+
let denseVectors: number[][] = [];
|
|
231
|
+
let encodeSparse: (
|
|
232
|
+
input: string,
|
|
233
|
+
) => ReturnType<typeof generateSparseEmbedding> = generateSparseEmbedding;
|
|
187
234
|
if (seeds.length > 0) {
|
|
188
235
|
const embedded = await embedWithBackend(
|
|
189
236
|
config,
|
|
190
237
|
seeds.map((s) => s.content),
|
|
191
238
|
);
|
|
192
|
-
|
|
239
|
+
denseVectors = await Promise.all(
|
|
193
240
|
embedded.vectors.map((v) =>
|
|
194
241
|
applyCorrectionIfCalibrated(v, embedded.provider, embedded.model),
|
|
195
242
|
),
|
|
@@ -203,14 +250,25 @@ async function runSeedOnce(): Promise<void> {
|
|
|
203
250
|
// entirely. Fall back to the legacy TF encoder only during the cold-start
|
|
204
251
|
// window before corpus stats finish building.
|
|
205
252
|
const corpusStats = getConceptPageCorpusStats();
|
|
206
|
-
|
|
253
|
+
encodeSparse = (input: string) =>
|
|
207
254
|
corpusStats
|
|
208
255
|
? generateBm25DocEmbedding(input, corpusStats, {
|
|
209
256
|
k1: config.memory.v2.bm25_k1,
|
|
210
257
|
b: config.memory.v2.bm25_b,
|
|
211
258
|
})
|
|
212
259
|
: generateSparseEmbedding(input);
|
|
260
|
+
}
|
|
213
261
|
|
|
262
|
+
if (generation !== requestedSeedGeneration) {
|
|
263
|
+
log.info(
|
|
264
|
+
{ generation, latestGeneration: requestedSeedGeneration },
|
|
265
|
+
"Skipping stale v2 skill seed result",
|
|
266
|
+
);
|
|
267
|
+
lastSeedError = null;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (seeds.length > 0) {
|
|
214
272
|
const now = Date.now();
|
|
215
273
|
await Promise.all(
|
|
216
274
|
seeds.map((seed, i) =>
|
|
@@ -233,6 +291,25 @@ async function runSeedOnce(): Promise<void> {
|
|
|
233
291
|
// uninstalled catalog skills should exist, so skip pruning entirely to
|
|
234
292
|
// avoid aggressively removing previously-seeded catalog skill embeddings.
|
|
235
293
|
if (catalogAvailable) {
|
|
294
|
+
// Tag legacy skill points missing `payload.kind` before pruning so the
|
|
295
|
+
// kind-scoped prune can see them. Once-per-process; the backfill is
|
|
296
|
+
// idempotent (server-side `is_empty` filter), so a partial failure
|
|
297
|
+
// converges on retry.
|
|
298
|
+
if (!legacyKindBackfillDone) {
|
|
299
|
+
try {
|
|
300
|
+
await backfillKindOnPointsWithPrefix(
|
|
301
|
+
SKILL_SLUG_PREFIX,
|
|
302
|
+
SKILL_PAYLOAD_KIND,
|
|
303
|
+
knownSkillIds,
|
|
304
|
+
);
|
|
305
|
+
legacyKindBackfillDone = true;
|
|
306
|
+
} catch (err) {
|
|
307
|
+
log.warn(
|
|
308
|
+
{ err },
|
|
309
|
+
"Failed to backfill kind on legacy skill points — pruning may leave orphans this run",
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
236
313
|
await pruneSlugsWithPrefixExcept(
|
|
237
314
|
SKILL_SLUG_PREFIX,
|
|
238
315
|
seeds.map((s) => s.id),
|
|
@@ -265,12 +342,16 @@ async function runSeedOnce(): Promise<void> {
|
|
|
265
342
|
* Accepts either a bare skill id (`example-skill`) or its unified-collection
|
|
266
343
|
* slug (`skills/example-skill`) so render-side callers can pass through what
|
|
267
344
|
* they have without a manual prefix strip.
|
|
345
|
+
*
|
|
346
|
+
* Returns a frozen copy so callers cannot mutate the underlying cache entry
|
|
347
|
+
* — matches the defensive-copy contract of `listSkillEntries`.
|
|
268
348
|
*/
|
|
269
349
|
export function getSkillCapability(idOrSlug: string): SkillEntry | null {
|
|
270
350
|
const id = idOrSlug.startsWith(SKILL_SLUG_PREFIX)
|
|
271
351
|
? idOrSlug.slice(SKILL_SLUG_PREFIX.length)
|
|
272
352
|
: idOrSlug;
|
|
273
|
-
|
|
353
|
+
const entry = entries?.get(id);
|
|
354
|
+
return entry ? Object.freeze({ ...entry }) : null;
|
|
274
355
|
}
|
|
275
356
|
|
|
276
357
|
/** True iff the slug refers to a skill entry in the unified collection. */
|
|
@@ -280,8 +361,9 @@ export function isSkillSlug(slug: string): boolean {
|
|
|
280
361
|
|
|
281
362
|
/**
|
|
282
363
|
* Snapshot of the in-process skill cache, sorted by skill id (ASCII order)
|
|
283
|
-
* for determinism. Returns a freshly allocated array
|
|
284
|
-
* cannot mutate the underlying cache
|
|
364
|
+
* for determinism. Returns a freshly allocated array of frozen entry copies
|
|
365
|
+
* on each call, so callers cannot mutate the underlying cache — neither by
|
|
366
|
+
* reassigning the array nor by writing through entry fields.
|
|
285
367
|
*
|
|
286
368
|
* The cache is replaced atomically by `seedV2SkillEntries`, so a snapshot
|
|
287
369
|
* may be stale once a subsequent seed run completes. Callers that need
|
|
@@ -289,15 +371,18 @@ export function isSkillSlug(slug: string): boolean {
|
|
|
289
371
|
*/
|
|
290
372
|
export function listSkillEntries(): SkillEntry[] {
|
|
291
373
|
if (!entries) return [];
|
|
292
|
-
return [...entries.values()]
|
|
293
|
-
a.id < b.id ? -1 : a.id > b.id ? 1 : 0
|
|
294
|
-
|
|
374
|
+
return [...entries.values()]
|
|
375
|
+
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
|
376
|
+
.map((entry) => Object.freeze({ ...entry }));
|
|
295
377
|
}
|
|
296
378
|
|
|
297
379
|
/** @internal Test-only: clear the module-level cache. */
|
|
298
380
|
export function _resetSkillStoreForTests(): void {
|
|
299
381
|
entries = null;
|
|
300
|
-
|
|
301
|
-
|
|
382
|
+
requestedSeedGeneration = 0;
|
|
383
|
+
processedSeedGeneration = 0;
|
|
384
|
+
activeSeedDrain = null;
|
|
385
|
+
seedWaiters.splice(0, seedWaiters.length);
|
|
302
386
|
lastSeedError = null;
|
|
387
|
+
legacyKindBackfillDone = false;
|
|
303
388
|
}
|
|
@@ -35,9 +35,9 @@ const MEMORY_V2_STATIC_BLOCKS: readonly MemoryV2StaticBlock[] = [
|
|
|
35
35
|
];
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Build the v2 static memory block, gated on `config.memory.
|
|
39
|
-
* Empty/missing files are skipped; returns `null`
|
|
40
|
-
* every file is empty.
|
|
38
|
+
* Build the v2 static memory block, gated on `config.memory.enabled` and
|
|
39
|
+
* `config.memory.v2.enabled`. Empty/missing files are skipped; returns `null`
|
|
40
|
+
* when either gate is off or every file is empty.
|
|
41
41
|
*/
|
|
42
42
|
export function readMemoryV2StaticContent(): string | null {
|
|
43
43
|
let config;
|
|
@@ -46,7 +46,7 @@ export function readMemoryV2StaticContent(): string | null {
|
|
|
46
46
|
} catch {
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
|
-
if (!config.memory.v2.enabled) {
|
|
49
|
+
if (!config.memory.enabled || !config.memory.v2.enabled) {
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
52
|
|
package/src/memory/v2/types.ts
CHANGED
|
@@ -115,3 +115,26 @@ export interface SkillEntry {
|
|
|
115
115
|
id: string;
|
|
116
116
|
content: string;
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// CLI-command entries (synthetic concept-collection rows, not on-disk pages)
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Per-CLI-subcommand capability snapshot held in-process and embedded into the
|
|
125
|
+
* unified `memory_v2_concept_pages` Qdrant collection under the slug
|
|
126
|
+
* `cli-commands/<name>`. `content` is the full `helpInformation()` output for
|
|
127
|
+
* the top-level subcommand — the embedding target, intentionally uncapped so
|
|
128
|
+
* activation hints in flag descriptions and examples carry semantic weight.
|
|
129
|
+
* `description` is the one-line Commander description, rendered terse in
|
|
130
|
+
* `### CLI Commands You Can Use` so the injection block stays compact even
|
|
131
|
+
* for verbose `--help` outputs.
|
|
132
|
+
*
|
|
133
|
+
* Plain interface (no Zod) — same in-process-only justification as
|
|
134
|
+
* `SkillEntry`.
|
|
135
|
+
*/
|
|
136
|
+
export interface CliCommandEntry {
|
|
137
|
+
id: string;
|
|
138
|
+
description: string;
|
|
139
|
+
content: string;
|
|
140
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { ChannelReplyPayload } from "@vellumai/gateway-client";
|
|
4
|
+
|
|
5
|
+
import type { A2ATask, Artifact } from "../../../../a2a/protocol-types.js";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Mock state
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
let completedTask: A2ATask | null = null;
|
|
12
|
+
let completeWithArtifactsCalls: Array<{
|
|
13
|
+
taskId: string;
|
|
14
|
+
artifacts: Artifact[];
|
|
15
|
+
}> = [];
|
|
16
|
+
let pushUrlByTaskId: Record<string, string | null> = {};
|
|
17
|
+
let completeError: Error | null = null;
|
|
18
|
+
|
|
19
|
+
const fetchCalls: Array<{
|
|
20
|
+
url: string;
|
|
21
|
+
init: RequestInit;
|
|
22
|
+
}> = [];
|
|
23
|
+
let fetchResponses: Array<{ ok: boolean; status: number; body: string }> = [];
|
|
24
|
+
let fetchCallIndex = 0;
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Mocks
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
const defaultTask: A2ATask = {
|
|
31
|
+
id: "task-123",
|
|
32
|
+
status: { state: "completed", timestamp: new Date().toISOString() },
|
|
33
|
+
artifacts: [
|
|
34
|
+
{
|
|
35
|
+
artifact_id: "art-1",
|
|
36
|
+
parts: [{ kind: "text", text: "Hello from assistant" }],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
mock.module("../../../../a2a/task-store.js", () => ({
|
|
42
|
+
completeWithArtifacts: (taskId: string, artifacts: Artifact[]): A2ATask => {
|
|
43
|
+
completeWithArtifactsCalls.push({ taskId, artifacts });
|
|
44
|
+
if (completeError) throw completeError;
|
|
45
|
+
return completedTask ?? defaultTask;
|
|
46
|
+
},
|
|
47
|
+
getPushUrl: (taskId: string): string | null => {
|
|
48
|
+
return pushUrlByTaskId[taskId] ?? null;
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
mock.module("../../../../util/logger.js", () => ({
|
|
53
|
+
getLogger: () => ({
|
|
54
|
+
debug: () => {},
|
|
55
|
+
info: () => {},
|
|
56
|
+
warn: () => {},
|
|
57
|
+
error: () => {},
|
|
58
|
+
}),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Intercept global fetch for push notification testing
|
|
62
|
+
const originalFetch = globalThis.fetch;
|
|
63
|
+
|
|
64
|
+
// Import the module under test AFTER mocks are set up
|
|
65
|
+
const { deliverA2AReply } = await import("../deliver.js");
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Setup / teardown
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
completedTask = null;
|
|
73
|
+
completeWithArtifactsCalls = [];
|
|
74
|
+
pushUrlByTaskId = {};
|
|
75
|
+
completeError = null;
|
|
76
|
+
fetchCalls.length = 0;
|
|
77
|
+
fetchResponses = [];
|
|
78
|
+
fetchCallIndex = 0;
|
|
79
|
+
|
|
80
|
+
globalThis.fetch = (async (
|
|
81
|
+
input: string | URL | Request,
|
|
82
|
+
init?: RequestInit,
|
|
83
|
+
) => {
|
|
84
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
85
|
+
fetchCalls.push({ url, init: init ?? {} });
|
|
86
|
+
const responseSpec = fetchResponses[fetchCallIndex++] ?? {
|
|
87
|
+
ok: true,
|
|
88
|
+
status: 200,
|
|
89
|
+
body: "{}",
|
|
90
|
+
};
|
|
91
|
+
return new Response(responseSpec.body, {
|
|
92
|
+
status: responseSpec.status,
|
|
93
|
+
statusText: responseSpec.ok ? "OK" : "Error",
|
|
94
|
+
});
|
|
95
|
+
}) as typeof fetch;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
globalThis.fetch = originalFetch;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Tests
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
describe("deliverA2AReply", () => {
|
|
107
|
+
const baseCallbackUrl = "https://example.com/deliver/a2a?taskId=task-123";
|
|
108
|
+
|
|
109
|
+
test("completes task with text artifact", async () => {
|
|
110
|
+
const payload: ChannelReplyPayload = {
|
|
111
|
+
chatId: "chat-1",
|
|
112
|
+
text: "Hello from the assistant",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const result = await deliverA2AReply(baseCallbackUrl, payload);
|
|
116
|
+
|
|
117
|
+
expect(result.ok).toBe(true);
|
|
118
|
+
expect(completeWithArtifactsCalls).toHaveLength(1);
|
|
119
|
+
expect(completeWithArtifactsCalls[0].taskId).toBe("task-123");
|
|
120
|
+
expect(completeWithArtifactsCalls[0].artifacts).toHaveLength(1);
|
|
121
|
+
expect(completeWithArtifactsCalls[0].artifacts[0].parts).toEqual([
|
|
122
|
+
{ kind: "text", text: "Hello from the assistant" },
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("completes task with file attachments", async () => {
|
|
127
|
+
const payload: ChannelReplyPayload = {
|
|
128
|
+
chatId: "chat-1",
|
|
129
|
+
text: "Here is a file",
|
|
130
|
+
attachments: [
|
|
131
|
+
{
|
|
132
|
+
id: "att-1",
|
|
133
|
+
filename: "report.pdf",
|
|
134
|
+
mimeType: "application/pdf",
|
|
135
|
+
sizeBytes: 1024,
|
|
136
|
+
kind: "file",
|
|
137
|
+
data: "data:application/pdf;base64,abc123",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = await deliverA2AReply(baseCallbackUrl, payload);
|
|
143
|
+
|
|
144
|
+
expect(result.ok).toBe(true);
|
|
145
|
+
expect(completeWithArtifactsCalls).toHaveLength(1);
|
|
146
|
+
const parts = completeWithArtifactsCalls[0].artifacts[0].parts;
|
|
147
|
+
expect(parts).toHaveLength(2);
|
|
148
|
+
expect(parts[0]).toEqual({
|
|
149
|
+
kind: "text",
|
|
150
|
+
text: "Here is a file",
|
|
151
|
+
});
|
|
152
|
+
expect(parts[1]).toEqual({
|
|
153
|
+
kind: "file",
|
|
154
|
+
filename: "report.pdf",
|
|
155
|
+
media_type: "application/pdf",
|
|
156
|
+
url: "data:application/pdf;base64,abc123",
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("returns ok: false when taskId is missing from URL", async () => {
|
|
161
|
+
const result = await deliverA2AReply("https://example.com/deliver/a2a", {
|
|
162
|
+
chatId: "chat-1",
|
|
163
|
+
text: "Hello",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(result.ok).toBe(false);
|
|
167
|
+
expect(completeWithArtifactsCalls).toHaveLength(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("returns ok: true when payload has no content", async () => {
|
|
171
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
172
|
+
chatId: "chat-1",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(result.ok).toBe(true);
|
|
176
|
+
expect(completeWithArtifactsCalls).toHaveLength(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("returns ok: false when task completion throws", async () => {
|
|
180
|
+
completeError = new Error("A2A task not found: task-123");
|
|
181
|
+
|
|
182
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
183
|
+
chatId: "chat-1",
|
|
184
|
+
text: "Hello",
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(result.ok).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("returns ok: false when task is already terminal", async () => {
|
|
191
|
+
completeError = new Error(
|
|
192
|
+
'Cannot transition task task-123 from terminal state "completed" to "completed"',
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
196
|
+
chatId: "chat-1",
|
|
197
|
+
text: "Hello",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(result.ok).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("push notifications", () => {
|
|
204
|
+
test("POSTs completed task to push URL", async () => {
|
|
205
|
+
pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
|
|
206
|
+
fetchResponses = [{ ok: true, status: 200, body: "{}" }];
|
|
207
|
+
|
|
208
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
209
|
+
chatId: "chat-1",
|
|
210
|
+
text: "Done",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(result.ok).toBe(true);
|
|
214
|
+
|
|
215
|
+
// Wait for the fire-and-forget push to complete
|
|
216
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
217
|
+
|
|
218
|
+
expect(fetchCalls).toHaveLength(1);
|
|
219
|
+
expect(fetchCalls[0].url).toBe("https://requester.example.com/push");
|
|
220
|
+
expect(fetchCalls[0].init.method).toBe("POST");
|
|
221
|
+
|
|
222
|
+
const headers = fetchCalls[0].init.headers as Record<string, string>;
|
|
223
|
+
expect(headers["Content-Type"]).toBe("application/a2a+json");
|
|
224
|
+
expect(headers["A2A-Version"]).toBe("1.0");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("does not push when no push URL configured", async () => {
|
|
228
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
229
|
+
chatId: "chat-1",
|
|
230
|
+
text: "Done",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(result.ok).toBe(true);
|
|
234
|
+
|
|
235
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
236
|
+
|
|
237
|
+
expect(fetchCalls).toHaveLength(0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("push failure does not affect delivery result", async () => {
|
|
241
|
+
pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
|
|
242
|
+
// All retries fail with 500
|
|
243
|
+
fetchResponses = Array(4).fill({
|
|
244
|
+
ok: false,
|
|
245
|
+
status: 500,
|
|
246
|
+
body: "Internal Server Error",
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
250
|
+
chatId: "chat-1",
|
|
251
|
+
text: "Done",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Delivery still succeeds even though push will fail
|
|
255
|
+
expect(result.ok).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("stops retrying on non-retryable client error", async () => {
|
|
259
|
+
pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
|
|
260
|
+
fetchResponses = [{ ok: false, status: 404, body: "Not Found" }];
|
|
261
|
+
|
|
262
|
+
await deliverA2AReply(baseCallbackUrl, {
|
|
263
|
+
chatId: "chat-1",
|
|
264
|
+
text: "Done",
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Wait for the fire-and-forget push to settle
|
|
268
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
269
|
+
|
|
270
|
+
// Should only attempt once on a 4xx (non-429) error
|
|
271
|
+
expect(fetchCalls).toHaveLength(1);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|