@vellumai/assistant 0.6.5 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -1
- package/ARCHITECTURE.md +15 -17
- package/Dockerfile +6 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
- package/docs/architecture/integrations.md +32 -39
- package/docs/architecture/memory.md +25 -30
- package/docs/architecture/security.md +7 -6
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/plugins.md +761 -0
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/openapi.yaml +212 -68
- package/package.json +1 -1
- package/src/__tests__/app-compiler.test.ts +57 -0
- package/src/__tests__/approval-cascade.test.ts +7 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
- package/src/__tests__/avatar-generator.test.ts +4 -2
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/catalog-cache.test.ts +69 -0
- package/src/__tests__/checker.test.ts +459 -171
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/compaction-events.test.ts +501 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
- package/src/__tests__/config-model-image-provider.test.ts +110 -0
- package/src/__tests__/config-schema.test.ts +22 -9
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/contacts-tools.test.ts +26 -0
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-window-manager.test.ts +355 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
- package/src/__tests__/conversation-agent-loop.test.ts +30 -141
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
- package/src/__tests__/conversation-pairing.test.ts +174 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
- package/src/__tests__/conversation-process-callsite.test.ts +3 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
- package/src/__tests__/conversation-queue.test.ts +29 -14
- package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
- package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-slash-queue.test.ts +7 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
- package/src/__tests__/conversation-speed-override.test.ts +6 -1
- package/src/__tests__/conversation-title-service.test.ts +116 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +2 -2
- package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
- package/src/__tests__/empty-response-pipeline.test.ts +305 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
- package/src/__tests__/first-greeting.test.ts +247 -5
- package/src/__tests__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
- package/src/__tests__/host-proxy-interface.test.ts +36 -2
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/injector-chain.test.ts +526 -0
- package/src/__tests__/intent-routing.test.ts +0 -26
- package/src/__tests__/llm-call-pipeline.test.ts +285 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +5 -68
- package/src/__tests__/model-intents.test.ts +4 -2
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- package/src/__tests__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
- package/src/__tests__/oauth-apps-routes.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +14 -12
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
- package/src/__tests__/oauth-providers-routes.test.ts +3 -2
- package/src/__tests__/oauth-store.test.ts +41 -76
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -0
- package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +377 -0
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/platform.test.ts +5 -2
- package/src/__tests__/plugin-bootstrap.test.ts +483 -0
- package/src/__tests__/plugin-registry.test.ts +273 -0
- package/src/__tests__/plugin-route-contribution.test.ts +288 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +44 -12
- package/src/__tests__/proxy-approval-callback.test.ts +69 -8
- package/src/__tests__/reaction-persistence.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +0 -2
- package/src/__tests__/schedule-routes.test.ts +131 -1
- package/src/__tests__/scheduler-recurrence.test.ts +14 -70
- package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
- package/src/__tests__/secret-detection-handler.test.ts +0 -10
- package/src/__tests__/shell-identity.test.ts +0 -134
- package/src/__tests__/suggestion-routes.test.ts +103 -4
- package/src/__tests__/task-memory-cleanup.test.ts +1 -0
- package/src/__tests__/task-scheduler.test.ts +3 -15
- package/src/__tests__/test-preload.ts +11 -0
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
- package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
- package/src/__tests__/tool-executor.test.ts +141 -0
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- package/src/__tests__/user-plugin-loader.test.ts +191 -0
- package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-policy.test.ts +21 -3
- package/src/agent/loop.ts +340 -102
- package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
- package/src/approvals/guardian-request-resolvers.ts +80 -0
- package/src/backup/__tests__/backup-worker.test.ts +2 -13
- package/src/backup/backup-worker.ts +3 -15
- package/src/bundler/app-compiler.ts +84 -1
- package/src/calls/call-state.ts +2 -2
- package/src/channels/__tests__/types.test.ts +3 -3
- package/src/channels/types.ts +6 -4
- package/src/cli/__tests__/notifications.test.ts +87 -211
- package/src/cli/commands/__tests__/backup.test.ts +1 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
- package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
- package/src/cli/commands/backup.ts +2 -2
- package/src/cli/commands/clients.ts +138 -0
- package/src/cli/commands/completions.ts +2 -9
- package/src/cli/commands/conversations.ts +55 -7
- package/src/cli/commands/image-generation.ts +33 -34
- package/src/cli/commands/notifications.ts +68 -103
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/status.ts +46 -36
- package/src/cli/commands/skills.ts +3 -4
- package/src/cli/program.ts +25 -29
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-tool-registry.ts +0 -15
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +19 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/conversations.ts +16 -0
- package/src/config/schemas/llm.ts +2 -3
- package/src/config/schemas/security.ts +6 -6
- package/src/config/schemas/tts.ts +11 -0
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +94 -5
- package/src/context/__tests__/compact-prompt.test.ts +27 -9
- package/src/context/prompts/compact.md +26 -12
- package/src/context/tool-result-truncation.ts +3 -63
- package/src/context/window-manager.ts +190 -16
- package/src/credential-health/credential-health-service.ts +19 -6
- package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- package/src/daemon/config-watcher.ts +0 -2
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
- package/src/daemon/conversation-agent-loop.ts +984 -683
- package/src/daemon/conversation-history.ts +10 -19
- package/src/daemon/conversation-lifecycle.ts +37 -19
- package/src/daemon/conversation-notifiers.ts +2 -110
- package/src/daemon/conversation-process.ts +14 -7
- package/src/daemon/conversation-runtime-assembly.ts +532 -411
- package/src/daemon/conversation-tool-setup.ts +41 -4
- package/src/daemon/conversation.ts +80 -35
- package/src/daemon/external-plugins-bootstrap.ts +478 -0
- package/src/daemon/first-greeting.ts +191 -14
- package/src/daemon/handlers/config-model.ts +11 -0
- package/src/daemon/handlers/skills.ts +5 -1
- package/src/daemon/lifecycle.ts +33 -68
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +49 -0
- package/src/daemon/message-types/messages.ts +12 -0
- package/src/daemon/server.ts +5 -3
- package/src/daemon/shutdown-handlers.ts +2 -12
- package/src/daemon/tool-side-effects.ts +14 -56
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
- package/src/heartbeat/heartbeat-service.ts +24 -1
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- package/src/home/emit-feed-event.ts +7 -0
- package/src/home/feed-types.ts +41 -2
- package/src/home/rewrite-command-preview.ts +66 -0
- package/src/ipc/__tests__/socket-path.test.ts +11 -50
- package/src/ipc/cli-client.ts +1 -1
- package/src/ipc/cli-server.ts +3 -3
- package/src/ipc/gateway-client.ts +4 -1
- package/src/ipc/routes/browser-context.ts +2 -0
- package/src/ipc/routes/browser.ts +1 -0
- package/src/ipc/routes/get-contact.ts +16 -0
- package/src/ipc/routes/index.ts +14 -0
- package/src/ipc/routes/list-clients.ts +31 -0
- package/src/ipc/routes/merge-contacts.ts +17 -0
- package/src/ipc/routes/notification.ts +133 -0
- package/src/ipc/routes/rename-conversation.ts +59 -0
- package/src/ipc/routes/search-contacts.ts +19 -0
- package/src/ipc/routes/upsert-contact.ts +25 -0
- package/src/ipc/socket-path.ts +14 -38
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -0
- package/src/memory/conversation-crud.ts +48 -18
- package/src/memory/conversation-queries.ts +57 -4
- package/src/memory/conversation-title-service.ts +25 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-gemini.test.ts +41 -2
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/graph/bootstrap.test.ts +282 -0
- package/src/memory/graph/bootstrap.ts +8 -5
- package/src/memory/graph/extraction.ts +10 -2
- package/src/memory/graph/graph-search.test.ts +1 -0
- package/src/memory/graph/inspect.ts +2 -2
- package/src/memory/graph/retriever.ts +10 -3
- package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- package/src/memory/migrations/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/pkb/pkb-index.test.ts +1 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +65 -4
- package/src/memory/pkb/pkb-search.ts +40 -18
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +25 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/oauth.ts +4 -1
- package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
- package/src/messaging/providers/slack/render-transcript.ts +58 -0
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +1 -2
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +30 -14
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -108
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/permissions/approval-policy.test.ts +293 -18
- package/src/permissions/approval-policy.ts +110 -58
- package/src/permissions/arg-parser.test.ts +161 -0
- package/src/permissions/arg-parser.ts +141 -0
- package/src/permissions/bash-risk-classifier.test.ts +414 -2
- package/src/permissions/bash-risk-classifier.ts +303 -60
- package/src/permissions/checker.ts +157 -29
- package/src/permissions/command-registry.test.ts +239 -0
- package/src/permissions/command-registry.ts +234 -54
- package/src/permissions/defaults.ts +5 -4
- package/src/permissions/gateway-threshold-reader.ts +196 -0
- package/src/permissions/prompter.ts +4 -0
- package/src/permissions/risk-types.ts +61 -4
- package/src/permissions/schedule-risk-classifier.test.ts +129 -0
- package/src/permissions/schedule-risk-classifier.ts +85 -0
- package/src/permissions/shell-identity.ts +2 -42
- package/src/permissions/types.ts +2 -0
- package/src/permissions/workspace-policy.ts +8 -3
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +491 -0
- package/src/plugins/defaults/llm-call.ts +82 -0
- package/src/plugins/defaults/memory-retrieval.ts +226 -0
- package/src/plugins/defaults/overflow-reduce.ts +181 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +104 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +241 -0
- package/src/plugins/types.ts +1134 -0
- package/src/plugins/user-loader.ts +177 -0
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- package/src/providers/model-catalog.ts +52 -29
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
- package/src/providers/speech-to-text/xai-realtime.ts +39 -14
- package/src/runtime/AGENTS.md +25 -16
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
- package/src/runtime/__tests__/client-registry.test.ts +293 -0
- package/src/runtime/client-registry.ts +261 -0
- package/src/runtime/http-server.ts +77 -8
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +1 -22
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
- package/src/runtime/routes/approval-routes.ts +17 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
- package/src/runtime/routes/conversation-routes.ts +223 -116
- package/src/runtime/routes/inbound-message-handler.ts +88 -13
- package/src/runtime/routes/memory-item-routes.test.ts +1 -0
- package/src/runtime/routes/migration-routes.ts +0 -3
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
- package/src/runtime/routes/playground/deps.ts +56 -0
- package/src/runtime/routes/playground/force-compact.ts +73 -0
- package/src/runtime/routes/playground/guard.ts +37 -0
- package/src/runtime/routes/playground/index.ts +28 -0
- package/src/runtime/routes/playground/inject-failures.ts +159 -0
- package/src/runtime/routes/playground/reset-circuit.ts +115 -0
- package/src/runtime/routes/playground/seed-conversation.ts +139 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
- package/src/runtime/routes/playground/state.ts +78 -0
- package/src/runtime/routes/schedule-routes.ts +89 -8
- package/src/runtime/skill-route-registry.ts +75 -15
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +7 -1
- package/src/schedule/scheduler.ts +48 -8
- package/src/skills/catalog-cache.ts +12 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
- package/src/tools/browser/browser-execution.ts +88 -19
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/executor.ts +126 -74
- package/src/tools/network/script-proxy/session-manager.ts +37 -1
- package/src/tools/permission-checker.ts +98 -49
- package/src/tools/policy-context.ts +4 -0
- package/src/tools/registry.ts +140 -3
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +0 -51
- package/src/tools/system/avatar-generator.ts +6 -2
- package/src/tools/types.ts +28 -2
- package/src/util/platform.ts +7 -2
- package/src/util/pricing.ts +26 -3
- package/src/workspace/migrations/006-services-config.ts +2 -4
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- package/src/__tests__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- package/src/__tests__/send-notification-tool.test.ts +0 -83
- package/src/cli/commands/shotgun.ts +0 -266
- package/src/config/bundled-skills/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
- package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- package/src/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/oauth/scope-policy.ts +0 -89
- package/src/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/signals/shotgun.ts +0 -203
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `memoryRetrieval` plugin pipeline (PR 20).
|
|
3
|
+
*
|
|
4
|
+
* Covers the default terminal behavior, timeout handling, and custom-plugin
|
|
5
|
+
* substitution. Uses `mock.module` to stub the workspace PKB/NOW readers
|
|
6
|
+
* so the test doesn't touch the developer's real `~/.vellum`. The memory
|
|
7
|
+
* graph handle is a hand-rolled fake passed as a dependency — the default
|
|
8
|
+
* retriever only needs `prepareMemory`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
// Stub PKB/NOW readers BEFORE importing the module under test so the
|
|
14
|
+
// bindings resolve through the mock.
|
|
15
|
+
const readPkbContextMock = mock((): string | null => "pkb-default");
|
|
16
|
+
const readNowContextMock = mock((): string | null => "now-default");
|
|
17
|
+
mock.module("../daemon/conversation-runtime-assembly.js", () => ({
|
|
18
|
+
readPkbContext: readPkbContextMock,
|
|
19
|
+
readNowScratchpad: readNowContextMock,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import type { AssistantConfig } from "../config/schema.js";
|
|
23
|
+
import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
|
|
24
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
25
|
+
import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
26
|
+
import {
|
|
27
|
+
asDefaultGraphPayload,
|
|
28
|
+
DEFAULT_MEMORY_GRAPH_KIND,
|
|
29
|
+
type DefaultMemoryRetrievalDeps,
|
|
30
|
+
defaultMemoryRetrievalPlugin,
|
|
31
|
+
runDefaultMemoryRetrieval,
|
|
32
|
+
} from "../plugins/defaults/memory-retrieval.js";
|
|
33
|
+
import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
|
|
34
|
+
import {
|
|
35
|
+
getMiddlewaresFor,
|
|
36
|
+
registerPlugin,
|
|
37
|
+
resetPluginRegistryForTests,
|
|
38
|
+
} from "../plugins/registry.js";
|
|
39
|
+
import {
|
|
40
|
+
type MemoryArgs,
|
|
41
|
+
type MemoryResult,
|
|
42
|
+
type Middleware,
|
|
43
|
+
type Plugin,
|
|
44
|
+
PluginTimeoutError,
|
|
45
|
+
type TurnContext,
|
|
46
|
+
} from "../plugins/types.js";
|
|
47
|
+
import type { Message } from "../providers/types.js";
|
|
48
|
+
|
|
49
|
+
const trust: TrustContext = {
|
|
50
|
+
sourceChannel: "vellum",
|
|
51
|
+
trustClass: "guardian",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function makeTurnCtx(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
55
|
+
return {
|
|
56
|
+
requestId: "req-test",
|
|
57
|
+
conversationId: "conv-test",
|
|
58
|
+
turnIndex: 0,
|
|
59
|
+
trust,
|
|
60
|
+
...overrides,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function makeMemoryArgs(overrides: Partial<MemoryArgs> = {}): MemoryArgs {
|
|
65
|
+
return {
|
|
66
|
+
conversationId: "conv-test",
|
|
67
|
+
trustContext: trust,
|
|
68
|
+
turnIndex: 0,
|
|
69
|
+
...overrides,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Fake graph-memory whose `prepareMemory` returns a canonical result. The
|
|
75
|
+
* default retriever threads this return value through
|
|
76
|
+
* `MemoryResult.memoryGraphBlocks[0].result`, so tests can assert the block
|
|
77
|
+
* shape by comparing the embedded object identity.
|
|
78
|
+
*/
|
|
79
|
+
function makeFakeGraphMemory(overrides?: {
|
|
80
|
+
messages?: Message[];
|
|
81
|
+
injectedTokens?: number;
|
|
82
|
+
injectedBlockText?: string | null;
|
|
83
|
+
}): {
|
|
84
|
+
memory: ConversationGraphMemory;
|
|
85
|
+
prepareMemoryMock: ReturnType<typeof mock>;
|
|
86
|
+
} {
|
|
87
|
+
const returnValue = {
|
|
88
|
+
runMessages: overrides?.messages ?? [],
|
|
89
|
+
injectedTokens: overrides?.injectedTokens ?? 0,
|
|
90
|
+
latencyMs: 0,
|
|
91
|
+
mode: "none" as const,
|
|
92
|
+
injectedBlockText:
|
|
93
|
+
overrides?.injectedBlockText === undefined
|
|
94
|
+
? null
|
|
95
|
+
: overrides.injectedBlockText,
|
|
96
|
+
metrics: null,
|
|
97
|
+
};
|
|
98
|
+
const prepareMemoryMock = mock(async () => returnValue);
|
|
99
|
+
const memory = {
|
|
100
|
+
prepareMemory: prepareMemoryMock,
|
|
101
|
+
} as unknown as ConversationGraphMemory;
|
|
102
|
+
return { memory, prepareMemoryMock };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function makeDeps(
|
|
106
|
+
overrides: Partial<DefaultMemoryRetrievalDeps> = {},
|
|
107
|
+
): DefaultMemoryRetrievalDeps {
|
|
108
|
+
const { memory } = makeFakeGraphMemory();
|
|
109
|
+
return {
|
|
110
|
+
messages: [],
|
|
111
|
+
graphMemory: memory,
|
|
112
|
+
config: {} as AssistantConfig,
|
|
113
|
+
onEvent: () => {},
|
|
114
|
+
isTrustedActor: true,
|
|
115
|
+
...overrides,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
resetPluginRegistryForTests();
|
|
121
|
+
readPkbContextMock.mockReset();
|
|
122
|
+
readNowContextMock.mockReset();
|
|
123
|
+
readPkbContextMock.mockImplementation(() => "pkb-default");
|
|
124
|
+
readNowContextMock.mockImplementation(() => "now-default");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("runDefaultMemoryRetrieval", () => {
|
|
128
|
+
test("returns PKB, NOW, and a single graph block when the actor is trusted", async () => {
|
|
129
|
+
const { memory, prepareMemoryMock } = makeFakeGraphMemory();
|
|
130
|
+
const deps = makeDeps({ graphMemory: memory, isTrustedActor: true });
|
|
131
|
+
|
|
132
|
+
const result = await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
|
|
133
|
+
|
|
134
|
+
expect(result.pkbContent).toBe("pkb-default");
|
|
135
|
+
expect(result.nowContent).toBe("now-default");
|
|
136
|
+
expect(result.memoryGraphBlocks).toHaveLength(1);
|
|
137
|
+
expect(prepareMemoryMock).toHaveBeenCalledTimes(1);
|
|
138
|
+
|
|
139
|
+
const payload = asDefaultGraphPayload(result.memoryGraphBlocks);
|
|
140
|
+
expect(payload).not.toBeNull();
|
|
141
|
+
expect(payload?.kind).toBe(DEFAULT_MEMORY_GRAPH_KIND);
|
|
142
|
+
// The default retriever forwards the graph-memory return value
|
|
143
|
+
// verbatim under `payload.result` — consumers in the agent loop
|
|
144
|
+
// rely on that identity.
|
|
145
|
+
expect(payload?.result.mode).toBe("none");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("skips graph retrieval for untrusted actors", async () => {
|
|
149
|
+
const { memory, prepareMemoryMock } = makeFakeGraphMemory();
|
|
150
|
+
const deps = makeDeps({ graphMemory: memory, isTrustedActor: false });
|
|
151
|
+
|
|
152
|
+
const result = await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
|
|
153
|
+
|
|
154
|
+
expect(prepareMemoryMock).not.toHaveBeenCalled();
|
|
155
|
+
expect(result.memoryGraphBlocks).toEqual([]);
|
|
156
|
+
expect(result.pkbContent).toBe("pkb-default");
|
|
157
|
+
expect(result.nowContent).toBe("now-default");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("passes through null PKB and NOW when the files are absent", async () => {
|
|
161
|
+
readPkbContextMock.mockImplementation(() => null);
|
|
162
|
+
readNowContextMock.mockImplementation(() => null);
|
|
163
|
+
const deps = makeDeps();
|
|
164
|
+
|
|
165
|
+
const result = await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
|
|
166
|
+
|
|
167
|
+
expect(result.pkbContent).toBeNull();
|
|
168
|
+
expect(result.nowContent).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("asDefaultGraphPayload", () => {
|
|
173
|
+
test("returns null when the blocks array is empty", () => {
|
|
174
|
+
expect(asDefaultGraphPayload([])).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("returns null when the first block lacks the default discriminator", () => {
|
|
178
|
+
expect(asDefaultGraphPayload([{ kind: "custom" }])).toBeNull();
|
|
179
|
+
expect(asDefaultGraphPayload([{}])).toBeNull();
|
|
180
|
+
expect(asDefaultGraphPayload([null])).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("narrows blocks whose first entry carries the default discriminator", () => {
|
|
184
|
+
const payload = {
|
|
185
|
+
kind: DEFAULT_MEMORY_GRAPH_KIND,
|
|
186
|
+
result: { mode: "per-turn" } as never,
|
|
187
|
+
};
|
|
188
|
+
expect(asDefaultGraphPayload([payload])).toBe(payload);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe("memoryRetrieval pipeline — default vs custom plugin", () => {
|
|
193
|
+
test("default (no plugins registered) matches current retrieval exactly", async () => {
|
|
194
|
+
const deps = makeDeps();
|
|
195
|
+
const args = makeMemoryArgs();
|
|
196
|
+
const terminalDirect = await runDefaultMemoryRetrieval(args, deps);
|
|
197
|
+
|
|
198
|
+
// With an empty registry the pipeline runs the terminal directly. Use a
|
|
199
|
+
// fresh graph handle so `prepareMemory` call counts don't leak across
|
|
200
|
+
// the two invocations.
|
|
201
|
+
const deps2 = makeDeps();
|
|
202
|
+
const terminalViaPipeline = await runPipeline(
|
|
203
|
+
"memoryRetrieval",
|
|
204
|
+
getMiddlewaresFor("memoryRetrieval"),
|
|
205
|
+
(innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps2),
|
|
206
|
+
args,
|
|
207
|
+
makeTurnCtx(),
|
|
208
|
+
DEFAULT_TIMEOUTS.memoryRetrieval,
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
expect(terminalViaPipeline.pkbContent).toBe(terminalDirect.pkbContent);
|
|
212
|
+
expect(terminalViaPipeline.nowContent).toBe(terminalDirect.nowContent);
|
|
213
|
+
expect(terminalViaPipeline.memoryGraphBlocks).toHaveLength(
|
|
214
|
+
terminalDirect.memoryGraphBlocks.length,
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("with the default plugin registered, pipeline still produces default output", async () => {
|
|
219
|
+
registerPlugin(defaultMemoryRetrievalPlugin);
|
|
220
|
+
const deps = makeDeps();
|
|
221
|
+
const args = makeMemoryArgs();
|
|
222
|
+
|
|
223
|
+
const result = await runPipeline(
|
|
224
|
+
"memoryRetrieval",
|
|
225
|
+
getMiddlewaresFor("memoryRetrieval"),
|
|
226
|
+
(innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
|
|
227
|
+
args,
|
|
228
|
+
makeTurnCtx(),
|
|
229
|
+
DEFAULT_TIMEOUTS.memoryRetrieval,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(result.pkbContent).toBe("pkb-default");
|
|
233
|
+
expect(result.nowContent).toBe("now-default");
|
|
234
|
+
expect(result.memoryGraphBlocks).toHaveLength(1);
|
|
235
|
+
expect(asDefaultGraphPayload(result.memoryGraphBlocks)).not.toBeNull();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("custom plugin can replace all three sources via short-circuit", async () => {
|
|
239
|
+
const customBlock = { kind: "custom.source", text: "replacement" };
|
|
240
|
+
const customMiddleware: Middleware<MemoryArgs, MemoryResult> =
|
|
241
|
+
async function customRetriever() {
|
|
242
|
+
// Skip `next` entirely — the terminal never runs.
|
|
243
|
+
return {
|
|
244
|
+
pkbContent: "pkb-custom",
|
|
245
|
+
nowContent: "now-custom",
|
|
246
|
+
memoryGraphBlocks: [customBlock],
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const customPlugin: Plugin = {
|
|
251
|
+
manifest: {
|
|
252
|
+
name: "custom-memory-retrieval",
|
|
253
|
+
version: "0.0.1",
|
|
254
|
+
requires: { pluginRuntime: "v1", memoryApi: "v1" },
|
|
255
|
+
},
|
|
256
|
+
middleware: { memoryRetrieval: customMiddleware },
|
|
257
|
+
};
|
|
258
|
+
registerPlugin(customPlugin);
|
|
259
|
+
|
|
260
|
+
const deps = makeDeps();
|
|
261
|
+
const args = makeMemoryArgs();
|
|
262
|
+
|
|
263
|
+
const result = await runPipeline(
|
|
264
|
+
"memoryRetrieval",
|
|
265
|
+
getMiddlewaresFor("memoryRetrieval"),
|
|
266
|
+
(innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
|
|
267
|
+
args,
|
|
268
|
+
makeTurnCtx(),
|
|
269
|
+
DEFAULT_TIMEOUTS.memoryRetrieval,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
expect(result.pkbContent).toBe("pkb-custom");
|
|
273
|
+
expect(result.nowContent).toBe("now-custom");
|
|
274
|
+
expect(result.memoryGraphBlocks).toEqual([customBlock]);
|
|
275
|
+
// The terminal never ran, so the stubbed readers were NOT invoked.
|
|
276
|
+
expect(readPkbContextMock).not.toHaveBeenCalled();
|
|
277
|
+
expect(readNowContextMock).not.toHaveBeenCalled();
|
|
278
|
+
// And `asDefaultGraphPayload` must return null because the custom
|
|
279
|
+
// plugin supplied a block without the default discriminator — this is
|
|
280
|
+
// what drives the agent-loop escape hatch.
|
|
281
|
+
expect(asDefaultGraphPayload(result.memoryGraphBlocks)).toBeNull();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("timeout: terminal that hangs past the budget fails with PluginTimeoutError", async () => {
|
|
285
|
+
// Hang-prone middleware that never resolves; the runner arms a 5s timer
|
|
286
|
+
// by default, but the test overrides to a much smaller budget to keep
|
|
287
|
+
// the suite fast.
|
|
288
|
+
const hanging: Middleware<MemoryArgs, MemoryResult> =
|
|
289
|
+
async function hangingRetriever(_args, _next) {
|
|
290
|
+
return new Promise<MemoryResult>(() => {
|
|
291
|
+
// Never resolves — simulates a retriever that blocks on I/O.
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const plugin: Plugin = {
|
|
296
|
+
manifest: {
|
|
297
|
+
name: "hanging-memory-plugin",
|
|
298
|
+
version: "0.0.1",
|
|
299
|
+
requires: { pluginRuntime: "v1", memoryApi: "v1" },
|
|
300
|
+
},
|
|
301
|
+
middleware: { memoryRetrieval: hanging },
|
|
302
|
+
};
|
|
303
|
+
registerPlugin(plugin);
|
|
304
|
+
|
|
305
|
+
const deps = makeDeps();
|
|
306
|
+
const args = makeMemoryArgs();
|
|
307
|
+
|
|
308
|
+
let caught: unknown;
|
|
309
|
+
try {
|
|
310
|
+
await runPipeline(
|
|
311
|
+
"memoryRetrieval",
|
|
312
|
+
getMiddlewaresFor("memoryRetrieval"),
|
|
313
|
+
(innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
|
|
314
|
+
args,
|
|
315
|
+
makeTurnCtx(),
|
|
316
|
+
30, // tiny budget — real production path uses 5_000ms
|
|
317
|
+
);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
caught = err;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
expect(caught).toBeInstanceOf(PluginTimeoutError);
|
|
323
|
+
const timeoutErr = caught as PluginTimeoutError;
|
|
324
|
+
expect(timeoutErr.pipeline).toBe("memoryRetrieval");
|
|
325
|
+
// The runner records whatever `ctx.pluginName` is set to when the
|
|
326
|
+
// timer fires. The default pipeline doesn't bind a plugin name, so
|
|
327
|
+
// the attribution is undefined — still fail the turn cleanly.
|
|
328
|
+
expect(timeoutErr.message).toContain("memoryRetrieval");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("pipeline timeout aborts the signal threaded into prepareMemory", async () => {
|
|
332
|
+
// Regression for the cancellation gap: before `MemoryArgs.signal` was
|
|
333
|
+
// swapped by the pipeline's abort-linker, a pipeline timeout rejected
|
|
334
|
+
// the race but left `prepareMemory` running — mutating graph state
|
|
335
|
+
// after the turn had already errored. This test wires a long-running
|
|
336
|
+
// `prepareMemory` fake, arms a tight budget, and asserts the signal
|
|
337
|
+
// the terminal actually received reports `aborted === true` once the
|
|
338
|
+
// timer fires.
|
|
339
|
+
let capturedSignal: AbortSignal | undefined;
|
|
340
|
+
const hangingPrepare = mock(
|
|
341
|
+
(
|
|
342
|
+
_msgs: Message[],
|
|
343
|
+
_cfg: AssistantConfig,
|
|
344
|
+
signal: AbortSignal,
|
|
345
|
+
_onEvent: (msg: ServerMessage) => void,
|
|
346
|
+
) => {
|
|
347
|
+
capturedSignal = signal;
|
|
348
|
+
return new Promise((_resolve, reject) => {
|
|
349
|
+
signal.addEventListener("abort", () => reject(new Error("aborted")));
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
const graphMemory = {
|
|
354
|
+
prepareMemory: hangingPrepare,
|
|
355
|
+
} as unknown as ConversationGraphMemory;
|
|
356
|
+
const deps = makeDeps({ graphMemory });
|
|
357
|
+
const outerController = new AbortController();
|
|
358
|
+
const args = makeMemoryArgs({ signal: outerController.signal });
|
|
359
|
+
|
|
360
|
+
let caught: unknown;
|
|
361
|
+
try {
|
|
362
|
+
await runPipeline(
|
|
363
|
+
"memoryRetrieval",
|
|
364
|
+
getMiddlewaresFor("memoryRetrieval"),
|
|
365
|
+
(innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
|
|
366
|
+
args,
|
|
367
|
+
makeTurnCtx(),
|
|
368
|
+
30,
|
|
369
|
+
);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
caught = err;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
expect(caught).toBeInstanceOf(PluginTimeoutError);
|
|
375
|
+
expect(capturedSignal).toBeDefined();
|
|
376
|
+
// The signal the terminal observed must be the pipeline's linked
|
|
377
|
+
// signal (not the caller's bare signal), and it must be aborted so
|
|
378
|
+
// `prepareMemory` stops work instead of running to completion after
|
|
379
|
+
// the race has already rejected.
|
|
380
|
+
expect(capturedSignal).not.toBe(outerController.signal);
|
|
381
|
+
expect(capturedSignal!.aborted).toBe(true);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("onEvent is invoked by the default retriever's terminal path", async () => {
|
|
385
|
+
const received: ServerMessage[] = [];
|
|
386
|
+
const { memory } = makeFakeGraphMemory();
|
|
387
|
+
const deps = makeDeps({
|
|
388
|
+
graphMemory: memory,
|
|
389
|
+
onEvent: (msg) => received.push(msg),
|
|
390
|
+
isTrustedActor: true,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
|
|
394
|
+
|
|
395
|
+
// The fake graph doesn't emit events, but the event sink must be
|
|
396
|
+
// forwarded intact so the real retriever can use it. Verify by
|
|
397
|
+
// reaching into the mock assertion above (prepareMemoryMock called
|
|
398
|
+
// with `onEvent`).
|
|
399
|
+
expect(received).toEqual([]);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
* - Invalid GCS URL: a real https://evil.com URL is rejected with the
|
|
12
12
|
* redacted `Invalid URL: host` message; the raw URL is not echoed back
|
|
13
13
|
* in the response body.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
*
|
|
15
|
+
* Memory-ceiling coverage lives at the streaming-importer layer — see
|
|
16
|
+
* `vbundle-streaming-importer.test.ts` ("streamCommitImport — memory
|
|
17
|
+
* ceiling"). The URL handler adds only fixed HTTP/framing overhead on top
|
|
18
|
+
* of that pipeline, not bundle-size-proportional allocation.
|
|
19
19
|
*
|
|
20
20
|
* The raw-bytes ingress path is exercised by a separate test file,
|
|
21
21
|
* `migration-import-commit-http.test.ts`.
|
|
@@ -414,69 +414,6 @@ describe("handleMigrationImport — JSON {url} body", () => {
|
|
|
414
414
|
});
|
|
415
415
|
});
|
|
416
416
|
|
|
417
|
-
// ---------------------------------------------------------------------------
|
|
418
|
-
// Memory ceiling — streams a 100 MB fixture through and caps RSS.
|
|
419
|
-
// ---------------------------------------------------------------------------
|
|
420
|
-
|
|
421
|
-
function writeLargeFixtureToDisk(archivePath: string): void {
|
|
422
|
-
const CHUNK = 25 * 1024 * 1024;
|
|
423
|
-
const files = [0, 1, 2, 3].map((i) => ({
|
|
424
|
-
path: `workspace/big-${i}.bin`,
|
|
425
|
-
data: new Uint8Array(CHUNK).fill(0x41 + i),
|
|
426
|
-
}));
|
|
427
|
-
const { archive } = buildVBundle({ files });
|
|
428
|
-
writeFileSync(archivePath, archive);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
describe("handleMigrationImport — URL body memory ceiling", () => {
|
|
432
|
-
test("100 MB fixture streams in without pushing RSS past ~128 MB over baseline", async () => {
|
|
433
|
-
const archivePath = join(testParent, "fixture-large.vbundle");
|
|
434
|
-
writeLargeFixtureToDisk(archivePath);
|
|
435
|
-
|
|
436
|
-
const fixture = await startFixtureServer((_req, res) => {
|
|
437
|
-
res.writeHead(200, { "Content-Type": "application/octet-stream" });
|
|
438
|
-
createReadStream(archivePath).pipe(res);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
try {
|
|
442
|
-
// Force a GC opportunity before measuring baseline so stale fixture
|
|
443
|
-
// buffers don't count against the importer's budget. Bun exposes
|
|
444
|
-
// `Bun.gc` but not process.gc; gate on whether it exists.
|
|
445
|
-
const maybeBunGc = (
|
|
446
|
-
globalThis as { Bun?: { gc?: (sync?: boolean) => void } }
|
|
447
|
-
).Bun?.gc;
|
|
448
|
-
if (typeof maybeBunGc === "function") maybeBunGc(true);
|
|
449
|
-
|
|
450
|
-
const baselineRss = process.memoryUsage().rss;
|
|
451
|
-
|
|
452
|
-
const req = new Request("http://localhost/v1/migrations/import", {
|
|
453
|
-
method: "POST",
|
|
454
|
-
headers: { "Content-Type": "application/json" },
|
|
455
|
-
body: JSON.stringify({ url: makeFakeSignedUrl(fixture.port) }),
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
const res = await handleMigrationImport(req);
|
|
459
|
-
const body = (await res.json()) as ImportCommitResponse;
|
|
460
|
-
|
|
461
|
-
const peakRss = process.memoryUsage().rss;
|
|
462
|
-
|
|
463
|
-
expect(res.status).toBe(200);
|
|
464
|
-
expect(body.success).toBe(true);
|
|
465
|
-
|
|
466
|
-
// 128 MB ceiling: the URL handler pipes the HTTP response body
|
|
467
|
-
// through gunzip + tar-stream on top of the streaming importer's
|
|
468
|
-
// per-entry working set. The extra framing state is bigger than
|
|
469
|
-
// raw-stream importing (which fits in ~64 MB), so 128 MB keeps
|
|
470
|
-
// enough headroom to detect full-bundle buffering without
|
|
471
|
-
// flapping on normal streaming-mode overhead.
|
|
472
|
-
const delta = peakRss - baselineRss;
|
|
473
|
-
expect(delta).toBeLessThan(128 * 1024 * 1024);
|
|
474
|
-
} finally {
|
|
475
|
-
await fixture.close();
|
|
476
|
-
}
|
|
477
|
-
}, 90_000);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
417
|
// ---------------------------------------------------------------------------
|
|
481
418
|
// Gap A regression: no-swap success path must not append a stale
|
|
482
419
|
// "newer migration" warning sourced from the live DB. The warning would
|
|
@@ -31,9 +31,11 @@ describe("model intents", () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
test("falls back to provider default for unknown providers", () => {
|
|
34
|
-
expect(getProviderDefaultModel("unknown-provider")).toBe(
|
|
34
|
+
expect(getProviderDefaultModel("unknown-provider")).toBe(
|
|
35
|
+
"claude-sonnet-4-6",
|
|
36
|
+
);
|
|
35
37
|
expect(resolveModelIntent("unknown-provider", "quality-optimized")).toBe(
|
|
36
|
-
"claude-
|
|
38
|
+
"claude-sonnet-4-6",
|
|
37
39
|
);
|
|
38
40
|
});
|
|
39
41
|
});
|
|
@@ -593,7 +593,7 @@ describe("notification broadcaster", () => {
|
|
|
593
593
|
broadcaster.setOnConversationCreated((info) => createdCalls.push(info));
|
|
594
594
|
|
|
595
595
|
const signal = makeSignal({
|
|
596
|
-
sourceEventName: "schedule.
|
|
596
|
+
sourceEventName: "schedule.notify",
|
|
597
597
|
conversationMetadata: {
|
|
598
598
|
groupId: "system:scheduled",
|
|
599
599
|
source: "schedule",
|
|
@@ -607,7 +607,7 @@ describe("notification broadcaster", () => {
|
|
|
607
607
|
expect(createdCalls).toHaveLength(1);
|
|
608
608
|
expect(createdCalls[0].groupId).toBe("system:scheduled");
|
|
609
609
|
expect(createdCalls[0].source).toBe("schedule");
|
|
610
|
-
expect(createdCalls[0].sourceEventName).toBe("schedule.
|
|
610
|
+
expect(createdCalls[0].sourceEventName).toBe("schedule.notify");
|
|
611
611
|
});
|
|
612
612
|
|
|
613
613
|
test("onConversationCreated omits groupId and source when conversationMetadata is absent", async () => {
|
|
@@ -632,7 +632,7 @@ describe("notification broadcaster", () => {
|
|
|
632
632
|
const dispatchCalls: ConversationCreatedInfo[] = [];
|
|
633
633
|
|
|
634
634
|
const signal = makeSignal({
|
|
635
|
-
sourceEventName: "schedule.
|
|
635
|
+
sourceEventName: "schedule.notify",
|
|
636
636
|
conversationMetadata: {
|
|
637
637
|
groupId: "system:scheduled",
|
|
638
638
|
source: "schedule",
|
|
@@ -147,17 +147,6 @@ describe("notification decision strategy", () => {
|
|
|
147
147
|
expect(copy.telegram!.deliveryText).toBe("Take out the trash");
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
test("schedule.complete template uses name from payload", () => {
|
|
151
|
-
const signal = makeSignal({
|
|
152
|
-
sourceEventName: "schedule.complete",
|
|
153
|
-
contextPayload: { name: "Daily backup" },
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const copy = composeFallbackCopy(signal, channels);
|
|
157
|
-
expect(copy.vellum).toBeDefined();
|
|
158
|
-
expect(copy.vellum!.body).toContain("Daily backup");
|
|
159
|
-
});
|
|
160
|
-
|
|
161
150
|
test("unknown event name produces generic copy with urgency prefix", () => {
|
|
162
151
|
const signal = makeSignal({
|
|
163
152
|
sourceEventName: "some_novel.event",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test: recurring `schedule.notify` firings must not be
|
|
3
|
+
* deduplicated against prior firings of the same schedule.
|
|
4
|
+
*
|
|
5
|
+
* The scheduler supplies a unique per-firing dedupeKey
|
|
6
|
+
* (`schedule:notify:<id>:<timestamp>`) so `updateEventDedupeKey` is never
|
|
7
|
+
* called for schedule signals and `checkDedupe` never finds a matching
|
|
8
|
+
* row when the LLM decision engine generates a stable key like
|
|
9
|
+
* `schedule:notify:<id>`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
mock.module("../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
truncateForLog: (value: string) => value,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import { getDb, initializeDb } from "../memory/db.js";
|
|
23
|
+
import { notificationEvents } from "../memory/schema.js";
|
|
24
|
+
import { runDeterministicChecks } from "../notifications/deterministic-checks.js";
|
|
25
|
+
import { createEvent } from "../notifications/events-store.js";
|
|
26
|
+
import type { NotificationSignal } from "../notifications/signal.js";
|
|
27
|
+
import type { NotificationDecision } from "../notifications/types.js";
|
|
28
|
+
|
|
29
|
+
initializeDb();
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
getDb().delete(notificationEvents).run();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function makeSignal(
|
|
36
|
+
overrides?: Partial<NotificationSignal>,
|
|
37
|
+
): NotificationSignal {
|
|
38
|
+
return {
|
|
39
|
+
signalId: `sig-${crypto.randomUUID()}`,
|
|
40
|
+
createdAt: Date.now(),
|
|
41
|
+
sourceChannel: "scheduler",
|
|
42
|
+
sourceContextId: "schedule-123",
|
|
43
|
+
sourceEventName: "schedule.notify",
|
|
44
|
+
contextPayload: { scheduleId: "schedule-123", label: "Drink water" },
|
|
45
|
+
attentionHints: {
|
|
46
|
+
requiresAction: true,
|
|
47
|
+
urgency: "high",
|
|
48
|
+
isAsyncBackground: false,
|
|
49
|
+
visibleInSourceNow: false,
|
|
50
|
+
},
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function makeDecision(
|
|
56
|
+
overrides?: Partial<NotificationDecision>,
|
|
57
|
+
): NotificationDecision {
|
|
58
|
+
return {
|
|
59
|
+
shouldNotify: true,
|
|
60
|
+
selectedChannels: ["vellum"],
|
|
61
|
+
reasoningSummary: "Schedule reminder",
|
|
62
|
+
renderedCopy: {
|
|
63
|
+
vellum: { title: "Reminder", body: "Time to drink water" },
|
|
64
|
+
},
|
|
65
|
+
dedupeKey: "schedule:notify:schedule-123",
|
|
66
|
+
confidence: 0.9,
|
|
67
|
+
fallbackUsed: false,
|
|
68
|
+
...overrides,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe("recurring schedule.notify dedup", () => {
|
|
73
|
+
test("notify mode with timestamped producer keys is not blocked", async () => {
|
|
74
|
+
const stableKey = "schedule:notify:schedule-123";
|
|
75
|
+
const firstId = crypto.randomUUID();
|
|
76
|
+
const secondId = crypto.randomUUID();
|
|
77
|
+
|
|
78
|
+
const firstSignal = makeSignal({ signalId: firstId });
|
|
79
|
+
createEvent({
|
|
80
|
+
id: firstSignal.signalId,
|
|
81
|
+
sourceEventName: "schedule.notify",
|
|
82
|
+
sourceChannel: "scheduler",
|
|
83
|
+
sourceContextId: "schedule-123",
|
|
84
|
+
attentionHints: firstSignal.attentionHints,
|
|
85
|
+
payload: firstSignal.contextPayload,
|
|
86
|
+
dedupeKey: `schedule:notify:schedule-123:${Date.now() - 60_000}`,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const secondSignal = makeSignal({ signalId: secondId });
|
|
90
|
+
createEvent({
|
|
91
|
+
id: secondSignal.signalId,
|
|
92
|
+
sourceEventName: "schedule.notify",
|
|
93
|
+
sourceChannel: "scheduler",
|
|
94
|
+
sourceContextId: "schedule-123",
|
|
95
|
+
attentionHints: secondSignal.attentionHints,
|
|
96
|
+
payload: secondSignal.contextPayload,
|
|
97
|
+
dedupeKey: `schedule:notify:schedule-123:${Date.now()}`,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const decision = makeDecision({ dedupeKey: stableKey });
|
|
101
|
+
|
|
102
|
+
const result = await runDeterministicChecks(secondSignal, decision, {
|
|
103
|
+
connectedChannels: ["vellum"],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(result.passed).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|