@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,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `plugins/pipeline.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Onion composition order (outer → inner → terminal → inner → outer).
|
|
6
|
+
* - Short-circuit (middleware that omits `next` — terminal is skipped).
|
|
7
|
+
* - Error propagation (no internal try/catch — errors flow unchanged).
|
|
8
|
+
* - Timeout (breached budget rejects with `PluginTimeoutError`).
|
|
9
|
+
* - Log shape (one structured record per invocation, every field typed).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
|
|
15
|
+
import {
|
|
16
|
+
composeMiddleware,
|
|
17
|
+
DEFAULT_TIMEOUTS,
|
|
18
|
+
runPipeline,
|
|
19
|
+
} from "../plugins/pipeline.js";
|
|
20
|
+
import {
|
|
21
|
+
type Middleware,
|
|
22
|
+
PluginTimeoutError,
|
|
23
|
+
type TurnContext,
|
|
24
|
+
} from "../plugins/types.js";
|
|
25
|
+
|
|
26
|
+
// A minimal fake pino-compatible logger. The pipeline runner detects a
|
|
27
|
+
// `logger` slot on the context (shape `{ info(record, msg?) }`) and falls
|
|
28
|
+
// back to the module logger only when that slot is absent. Tests pass this
|
|
29
|
+
// fake in via `ctx.logger` so the runner emits into our capture buffer
|
|
30
|
+
// instead of real stderr.
|
|
31
|
+
type LogCall = [record: Record<string, unknown>, msg?: string];
|
|
32
|
+
|
|
33
|
+
function makeFakeLogger(): {
|
|
34
|
+
calls: LogCall[];
|
|
35
|
+
info: (record: Record<string, unknown>, msg?: string) => void;
|
|
36
|
+
warn: () => void;
|
|
37
|
+
error: () => void;
|
|
38
|
+
debug: () => void;
|
|
39
|
+
trace: () => void;
|
|
40
|
+
fatal: () => void;
|
|
41
|
+
} {
|
|
42
|
+
const calls: LogCall[] = [];
|
|
43
|
+
return {
|
|
44
|
+
calls,
|
|
45
|
+
info: (record, msg) => {
|
|
46
|
+
calls.push([record, msg]);
|
|
47
|
+
},
|
|
48
|
+
warn: () => {},
|
|
49
|
+
error: () => {},
|
|
50
|
+
debug: () => {},
|
|
51
|
+
trace: () => {},
|
|
52
|
+
fatal: () => {},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let fakeLogger = makeFakeLogger();
|
|
57
|
+
|
|
58
|
+
const trust: TrustContext = {
|
|
59
|
+
sourceChannel: "vellum",
|
|
60
|
+
trustClass: "guardian",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function makeCtx(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
64
|
+
return {
|
|
65
|
+
requestId: "req-test",
|
|
66
|
+
conversationId: "conv-test",
|
|
67
|
+
turnIndex: 3,
|
|
68
|
+
trust,
|
|
69
|
+
// The runner reads `(ctx as { logger?: unknown }).logger` — we cast
|
|
70
|
+
// through the partial type to attach it without widening TurnContext.
|
|
71
|
+
...({ logger: fakeLogger } as Partial<TurnContext>),
|
|
72
|
+
...overrides,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
fakeLogger = makeFakeLogger();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
type Args = { value: number };
|
|
81
|
+
type Result = { value: number };
|
|
82
|
+
|
|
83
|
+
describe("composeMiddleware", () => {
|
|
84
|
+
test("invokes layers in outer→inner→terminal→inner→outer order", async () => {
|
|
85
|
+
const trace: string[] = [];
|
|
86
|
+
|
|
87
|
+
const outer: Middleware<Args, Result> = async (args, next) => {
|
|
88
|
+
trace.push("outer:before");
|
|
89
|
+
const result = await next(args);
|
|
90
|
+
trace.push("outer:after");
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const inner: Middleware<Args, Result> = async (args, next) => {
|
|
95
|
+
trace.push("inner:before");
|
|
96
|
+
const result = await next(args);
|
|
97
|
+
trace.push("inner:after");
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const terminal = async (args: Args): Promise<Result> => {
|
|
102
|
+
trace.push("terminal");
|
|
103
|
+
return { value: args.value * 2 };
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const composed = composeMiddleware<Args, Result>([outer, inner], terminal);
|
|
107
|
+
const result = await composed({ value: 7 }, makeCtx());
|
|
108
|
+
|
|
109
|
+
expect(result).toEqual({ value: 14 });
|
|
110
|
+
expect(trace).toEqual([
|
|
111
|
+
"outer:before",
|
|
112
|
+
"inner:before",
|
|
113
|
+
"terminal",
|
|
114
|
+
"inner:after",
|
|
115
|
+
"outer:after",
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("middleware that omits `next` short-circuits the chain", async () => {
|
|
120
|
+
const trace: string[] = [];
|
|
121
|
+
|
|
122
|
+
const shortCircuit: Middleware<Args, Result> = async (_args, _next) => {
|
|
123
|
+
trace.push("short-circuit");
|
|
124
|
+
return { value: 99 };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const inner: Middleware<Args, Result> = async (args, next) => {
|
|
128
|
+
trace.push("inner");
|
|
129
|
+
return next(args);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const terminal = async (_args: Args): Promise<Result> => {
|
|
133
|
+
trace.push("terminal");
|
|
134
|
+
return { value: 0 };
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const composed = composeMiddleware<Args, Result>(
|
|
138
|
+
[shortCircuit, inner],
|
|
139
|
+
terminal,
|
|
140
|
+
);
|
|
141
|
+
const result = await composed({ value: 1 }, makeCtx());
|
|
142
|
+
|
|
143
|
+
expect(result).toEqual({ value: 99 });
|
|
144
|
+
expect(trace).toEqual(["short-circuit"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("empty middleware list reduces to the terminal handler", async () => {
|
|
148
|
+
const terminal = async (args: Args): Promise<Result> => ({
|
|
149
|
+
value: args.value + 1,
|
|
150
|
+
});
|
|
151
|
+
const composed = composeMiddleware<Args, Result>([], terminal);
|
|
152
|
+
const result = await composed({ value: 10 }, makeCtx());
|
|
153
|
+
expect(result).toEqual({ value: 11 });
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("runPipeline — error propagation", () => {
|
|
158
|
+
test("errors thrown by middleware bubble through unchanged", async () => {
|
|
159
|
+
class Boom extends Error {
|
|
160
|
+
override readonly name = "Boom";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const thrower: Middleware<Args, Result> = async () => {
|
|
164
|
+
throw new Boom("detonated");
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const terminal = async (_args: Args): Promise<Result> => {
|
|
168
|
+
throw new Error("terminal should not run");
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
await expect(
|
|
172
|
+
runPipeline(
|
|
173
|
+
"persistence",
|
|
174
|
+
[thrower],
|
|
175
|
+
terminal,
|
|
176
|
+
{ value: 1 },
|
|
177
|
+
makeCtx(),
|
|
178
|
+
DEFAULT_TIMEOUTS.persistence,
|
|
179
|
+
),
|
|
180
|
+
).rejects.toBeInstanceOf(Boom);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("errors thrown by the terminal handler bubble through unchanged", async () => {
|
|
184
|
+
const terminal = async (_args: Args): Promise<Result> => {
|
|
185
|
+
throw new TypeError("from terminal");
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
await expect(
|
|
189
|
+
runPipeline(
|
|
190
|
+
"persistence",
|
|
191
|
+
[],
|
|
192
|
+
terminal,
|
|
193
|
+
{ value: 1 },
|
|
194
|
+
makeCtx(),
|
|
195
|
+
DEFAULT_TIMEOUTS.persistence,
|
|
196
|
+
),
|
|
197
|
+
).rejects.toBeInstanceOf(TypeError);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("runPipeline — timeout", () => {
|
|
202
|
+
test("breached budget rejects with PluginTimeoutError carrying pipeline + plugin name", async () => {
|
|
203
|
+
const sleeper: Middleware<Args, Result> = async (_args, _next) =>
|
|
204
|
+
new Promise<Result>((resolve) => {
|
|
205
|
+
setTimeout(() => resolve({ value: 0 }), 200);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const terminal = async (_args: Args): Promise<Result> => ({ value: 0 });
|
|
209
|
+
|
|
210
|
+
let caught: unknown;
|
|
211
|
+
try {
|
|
212
|
+
await runPipeline(
|
|
213
|
+
"memoryRetrieval",
|
|
214
|
+
[sleeper],
|
|
215
|
+
terminal,
|
|
216
|
+
{ value: 1 },
|
|
217
|
+
makeCtx({ pluginName: "slow-plugin" }),
|
|
218
|
+
20,
|
|
219
|
+
);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
caught = err;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
expect(caught).toBeInstanceOf(PluginTimeoutError);
|
|
225
|
+
const tErr = caught as PluginTimeoutError;
|
|
226
|
+
expect(tErr.pipeline).toBe("memoryRetrieval");
|
|
227
|
+
expect(tErr.pluginName).toBe("slow-plugin");
|
|
228
|
+
expect(tErr.elapsedMs).toBeGreaterThanOrEqual(0);
|
|
229
|
+
expect(tErr.message).toContain("memoryRetrieval");
|
|
230
|
+
expect(tErr.message).toContain("slow-plugin");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("fast pipeline does not arm the timer redundantly", async () => {
|
|
234
|
+
const terminal = async (args: Args): Promise<Result> => ({
|
|
235
|
+
value: args.value,
|
|
236
|
+
});
|
|
237
|
+
const result = await runPipeline(
|
|
238
|
+
"historyRepair",
|
|
239
|
+
[],
|
|
240
|
+
terminal,
|
|
241
|
+
{ value: 42 },
|
|
242
|
+
makeCtx(),
|
|
243
|
+
DEFAULT_TIMEOUTS.historyRepair,
|
|
244
|
+
);
|
|
245
|
+
expect(result).toEqual({ value: 42 });
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("null timeout skips the race entirely", async () => {
|
|
249
|
+
// llmCall has DEFAULT_TIMEOUTS.llmCall === null — runner must not arm a
|
|
250
|
+
// timer. We verify by completing after an artificial 30ms wait and
|
|
251
|
+
// confirming success without interference.
|
|
252
|
+
const sleeper: Middleware<Args, Result> = async (args, next) =>
|
|
253
|
+
new Promise<Result>((resolve) => {
|
|
254
|
+
setTimeout(() => resolve(next(args)), 30);
|
|
255
|
+
});
|
|
256
|
+
const terminal = async (_args: Args): Promise<Result> => ({ value: 1 });
|
|
257
|
+
const result = await runPipeline(
|
|
258
|
+
"llmCall",
|
|
259
|
+
[sleeper],
|
|
260
|
+
terminal,
|
|
261
|
+
{ value: 0 },
|
|
262
|
+
makeCtx(),
|
|
263
|
+
DEFAULT_TIMEOUTS.llmCall,
|
|
264
|
+
);
|
|
265
|
+
expect(result).toEqual({ value: 1 });
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("runPipeline — timeout aborts linked signal", () => {
|
|
270
|
+
test("abort signal on args is fired when the timeout trips", async () => {
|
|
271
|
+
const callerController = new AbortController();
|
|
272
|
+
type SignalArgs = { value: number; signal: AbortSignal };
|
|
273
|
+
|
|
274
|
+
let observedAbortedAtCallStart = false;
|
|
275
|
+
let observedAbortedAtCallEnd = false;
|
|
276
|
+
|
|
277
|
+
const sleeper: Middleware<SignalArgs, Result> = async (
|
|
278
|
+
innerArgs,
|
|
279
|
+
_next,
|
|
280
|
+
) => {
|
|
281
|
+
observedAbortedAtCallStart = innerArgs.signal.aborted;
|
|
282
|
+
return new Promise<Result>((resolve, reject) => {
|
|
283
|
+
innerArgs.signal.addEventListener("abort", () => {
|
|
284
|
+
observedAbortedAtCallEnd = innerArgs.signal.aborted;
|
|
285
|
+
reject(Object.assign(new Error("aborted"), { name: "AbortError" }));
|
|
286
|
+
});
|
|
287
|
+
// Keep running past the timeout if the signal doesn't fire.
|
|
288
|
+
setTimeout(() => resolve({ value: 0 }), 500);
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const terminal = async (_args: SignalArgs): Promise<Result> => ({
|
|
293
|
+
value: 0,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
await expect(
|
|
297
|
+
runPipeline<SignalArgs, Result>(
|
|
298
|
+
"compaction",
|
|
299
|
+
[sleeper],
|
|
300
|
+
terminal,
|
|
301
|
+
{ value: 1, signal: callerController.signal },
|
|
302
|
+
makeCtx(),
|
|
303
|
+
15,
|
|
304
|
+
),
|
|
305
|
+
).rejects.toBeInstanceOf(PluginTimeoutError);
|
|
306
|
+
|
|
307
|
+
expect(observedAbortedAtCallStart).toBe(false);
|
|
308
|
+
expect(observedAbortedAtCallEnd).toBe(true);
|
|
309
|
+
// Caller's own signal must not be touched — the runner only aborts
|
|
310
|
+
// its internal linked signal, not the caller-owned controller.
|
|
311
|
+
expect(callerController.signal.aborted).toBe(false);
|
|
312
|
+
|
|
313
|
+
// Log record still reports timeout outcome + correct fields even though
|
|
314
|
+
// the inner middleware rejected with AbortError; the outer race still
|
|
315
|
+
// wins with the PluginTimeoutError.
|
|
316
|
+
const [record] = fakeLogger.calls[0]!;
|
|
317
|
+
expect(record.outcome).toBe("timeout");
|
|
318
|
+
expect(record.errorName).toBe("PluginTimeoutError");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("caller-side abort still propagates to the inner call", async () => {
|
|
322
|
+
const callerController = new AbortController();
|
|
323
|
+
type SignalArgs = { signal: AbortSignal };
|
|
324
|
+
|
|
325
|
+
let innerSignalAborted = false;
|
|
326
|
+
|
|
327
|
+
const sleeper: Middleware<SignalArgs, Result> = async (innerArgs) => {
|
|
328
|
+
return new Promise<Result>((_resolve, reject) => {
|
|
329
|
+
innerArgs.signal.addEventListener("abort", () => {
|
|
330
|
+
innerSignalAborted = innerArgs.signal.aborted;
|
|
331
|
+
reject(Object.assign(new Error("aborted"), { name: "AbortError" }));
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const terminal = async (_args: SignalArgs): Promise<Result> => ({
|
|
337
|
+
value: 0,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Fire a caller-side abort after a short delay.
|
|
341
|
+
setTimeout(() => callerController.abort(), 10);
|
|
342
|
+
|
|
343
|
+
await expect(
|
|
344
|
+
runPipeline<SignalArgs, Result>(
|
|
345
|
+
"compaction",
|
|
346
|
+
[sleeper],
|
|
347
|
+
terminal,
|
|
348
|
+
{ signal: callerController.signal },
|
|
349
|
+
makeCtx(),
|
|
350
|
+
10000,
|
|
351
|
+
),
|
|
352
|
+
).rejects.toMatchObject({ name: "AbortError" });
|
|
353
|
+
|
|
354
|
+
expect(innerSignalAborted).toBe(true);
|
|
355
|
+
expect(callerController.signal.aborted).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("args without an AbortSignal property is passed through unchanged", async () => {
|
|
359
|
+
// Sanity — pipelines that don't carry a signal (persistence, tokenEstimate)
|
|
360
|
+
// see identical args identity as before the abort-linking change.
|
|
361
|
+
const args: Args = { value: 42 };
|
|
362
|
+
let seen: Args | undefined;
|
|
363
|
+
const terminal = async (innerArgs: Args): Promise<Result> => {
|
|
364
|
+
seen = innerArgs;
|
|
365
|
+
return { value: innerArgs.value };
|
|
366
|
+
};
|
|
367
|
+
await runPipeline(
|
|
368
|
+
"persistence",
|
|
369
|
+
[],
|
|
370
|
+
terminal,
|
|
371
|
+
args,
|
|
372
|
+
makeCtx(),
|
|
373
|
+
DEFAULT_TIMEOUTS.persistence,
|
|
374
|
+
);
|
|
375
|
+
expect(seen).toBe(args);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("runPipeline — structured log record", () => {
|
|
380
|
+
test("success emits one record with every documented field present", async () => {
|
|
381
|
+
const namedOuter: Middleware<Args, Result> = async function outerMw(
|
|
382
|
+
args,
|
|
383
|
+
next,
|
|
384
|
+
) {
|
|
385
|
+
return next(args);
|
|
386
|
+
};
|
|
387
|
+
const terminal = async (args: Args): Promise<Result> => ({
|
|
388
|
+
value: args.value,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
await runPipeline(
|
|
392
|
+
"compaction",
|
|
393
|
+
[namedOuter],
|
|
394
|
+
terminal,
|
|
395
|
+
{ value: 7 },
|
|
396
|
+
makeCtx(),
|
|
397
|
+
DEFAULT_TIMEOUTS.compaction,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(fakeLogger.calls.length).toBe(1);
|
|
401
|
+
const [record, msg] = fakeLogger.calls[0]!;
|
|
402
|
+
expect(msg).toBe("plugin.pipeline");
|
|
403
|
+
expect(record.event).toBe("plugin.pipeline");
|
|
404
|
+
expect(record.pipeline).toBe("compaction");
|
|
405
|
+
expect(record.chain).toEqual(["outerMw"]);
|
|
406
|
+
expect(record.outcome).toBe("success");
|
|
407
|
+
expect(typeof record.durationMs).toBe("number");
|
|
408
|
+
expect(record.durationMs).toBeGreaterThanOrEqual(0);
|
|
409
|
+
expect(record.requestId).toBe("req-test");
|
|
410
|
+
expect(record.conversationId).toBe("conv-test");
|
|
411
|
+
expect(record.turnIndex).toBe(3);
|
|
412
|
+
// pluginName is only present when ctx carries one.
|
|
413
|
+
expect(record.pluginName).toBeUndefined();
|
|
414
|
+
// Error fields absent on success.
|
|
415
|
+
expect(record.errorName).toBeUndefined();
|
|
416
|
+
expect(record.errorMessage).toBeUndefined();
|
|
417
|
+
expect(record.errorStack).toBeUndefined();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("error path records outcome=error + error fields + plugin name", async () => {
|
|
421
|
+
class Boom extends Error {
|
|
422
|
+
override readonly name = "BoomError";
|
|
423
|
+
}
|
|
424
|
+
const thrower: Middleware<Args, Result> = async () => {
|
|
425
|
+
throw new Boom("kaboom");
|
|
426
|
+
};
|
|
427
|
+
const terminal = async (_args: Args): Promise<Result> => ({ value: 0 });
|
|
428
|
+
|
|
429
|
+
await expect(
|
|
430
|
+
runPipeline(
|
|
431
|
+
"toolError",
|
|
432
|
+
[thrower],
|
|
433
|
+
terminal,
|
|
434
|
+
{ value: 1 },
|
|
435
|
+
makeCtx({ pluginName: "noisy-plugin" }),
|
|
436
|
+
DEFAULT_TIMEOUTS.toolError,
|
|
437
|
+
),
|
|
438
|
+
).rejects.toBeInstanceOf(Boom);
|
|
439
|
+
|
|
440
|
+
expect(fakeLogger.calls.length).toBe(1);
|
|
441
|
+
const [record] = fakeLogger.calls[0]!;
|
|
442
|
+
expect(record.outcome).toBe("error");
|
|
443
|
+
expect(record.pipeline).toBe("toolError");
|
|
444
|
+
expect(record.errorName).toBe("BoomError");
|
|
445
|
+
expect(record.errorMessage).toBe("kaboom");
|
|
446
|
+
expect(typeof record.errorStack).toBe("string");
|
|
447
|
+
expect(record.pluginName).toBe("noisy-plugin");
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("timeout path records outcome=timeout + PluginTimeoutError fields", async () => {
|
|
451
|
+
const sleeper: Middleware<Args, Result> = async (_args, _next) =>
|
|
452
|
+
new Promise<Result>((resolve) => {
|
|
453
|
+
setTimeout(() => resolve({ value: 0 }), 200);
|
|
454
|
+
});
|
|
455
|
+
const terminal = async (_args: Args): Promise<Result> => ({ value: 0 });
|
|
456
|
+
|
|
457
|
+
await expect(
|
|
458
|
+
runPipeline(
|
|
459
|
+
"emptyResponse",
|
|
460
|
+
[sleeper],
|
|
461
|
+
terminal,
|
|
462
|
+
{ value: 1 },
|
|
463
|
+
makeCtx({ pluginName: "slow-plugin" }),
|
|
464
|
+
15,
|
|
465
|
+
),
|
|
466
|
+
).rejects.toBeInstanceOf(PluginTimeoutError);
|
|
467
|
+
|
|
468
|
+
expect(fakeLogger.calls.length).toBe(1);
|
|
469
|
+
const [record] = fakeLogger.calls[0]!;
|
|
470
|
+
expect(record.outcome).toBe("timeout");
|
|
471
|
+
expect(record.pipeline).toBe("emptyResponse");
|
|
472
|
+
expect(record.errorName).toBe("PluginTimeoutError");
|
|
473
|
+
expect(String(record.errorMessage)).toContain("emptyResponse");
|
|
474
|
+
expect(String(record.errorMessage)).toContain("slow-plugin");
|
|
475
|
+
expect(record.timeoutMs).toBe(15);
|
|
476
|
+
expect(record.pluginName).toBe("slow-plugin");
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test("null timeout omits timeoutMs field from the log record", async () => {
|
|
480
|
+
const terminal = async (args: Args): Promise<Result> => ({
|
|
481
|
+
value: args.value,
|
|
482
|
+
});
|
|
483
|
+
await runPipeline(
|
|
484
|
+
"llmCall",
|
|
485
|
+
[],
|
|
486
|
+
terminal,
|
|
487
|
+
{ value: 5 },
|
|
488
|
+
makeCtx(),
|
|
489
|
+
DEFAULT_TIMEOUTS.llmCall,
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
expect(fakeLogger.calls.length).toBe(1);
|
|
493
|
+
const [record] = fakeLogger.calls[0]!;
|
|
494
|
+
expect(record.pipeline).toBe("llmCall");
|
|
495
|
+
expect(record.outcome).toBe("success");
|
|
496
|
+
expect(record.timeoutMs).toBeUndefined();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test("turnIndex is omitted when unset on the context", async () => {
|
|
500
|
+
const terminal = async (_args: Args): Promise<Result> => ({ value: 0 });
|
|
501
|
+
const ctxNoTurn = {
|
|
502
|
+
requestId: "r",
|
|
503
|
+
conversationId: "c",
|
|
504
|
+
turnIndex: undefined as unknown as number,
|
|
505
|
+
trust,
|
|
506
|
+
logger: fakeLogger,
|
|
507
|
+
} as TurnContext;
|
|
508
|
+
await runPipeline(
|
|
509
|
+
"persistence",
|
|
510
|
+
[],
|
|
511
|
+
terminal,
|
|
512
|
+
{ value: 0 },
|
|
513
|
+
ctxNoTurn,
|
|
514
|
+
null,
|
|
515
|
+
);
|
|
516
|
+
const [record] = fakeLogger.calls[0]!;
|
|
517
|
+
expect(record.turnIndex).toBeUndefined();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("chain list has one entry per middleware in registration order", async () => {
|
|
521
|
+
const a: Middleware<Args, Result> = async function outerA(args, next) {
|
|
522
|
+
return next(args);
|
|
523
|
+
};
|
|
524
|
+
const b: Middleware<Args, Result> = async function middleB(args, next) {
|
|
525
|
+
return next(args);
|
|
526
|
+
};
|
|
527
|
+
const c: Middleware<Args, Result> = async function innerC(args, next) {
|
|
528
|
+
return next(args);
|
|
529
|
+
};
|
|
530
|
+
const terminal = async (args: Args): Promise<Result> => ({
|
|
531
|
+
value: args.value,
|
|
532
|
+
});
|
|
533
|
+
await runPipeline(
|
|
534
|
+
"tokenEstimate",
|
|
535
|
+
[a, b, c],
|
|
536
|
+
terminal,
|
|
537
|
+
{ value: 0 },
|
|
538
|
+
makeCtx(),
|
|
539
|
+
DEFAULT_TIMEOUTS.tokenEstimate,
|
|
540
|
+
);
|
|
541
|
+
const [record] = fakeLogger.calls[0]!;
|
|
542
|
+
expect(record.chain).toEqual(["outerA", "middleB", "innerC"]);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
describe("DEFAULT_TIMEOUTS", () => {
|
|
547
|
+
test("matches the design-doc table exactly", () => {
|
|
548
|
+
expect(DEFAULT_TIMEOUTS).toEqual({
|
|
549
|
+
turn: null,
|
|
550
|
+
llmCall: null,
|
|
551
|
+
toolExecute: null,
|
|
552
|
+
memoryRetrieval: null,
|
|
553
|
+
historyRepair: null,
|
|
554
|
+
tokenEstimate: null,
|
|
555
|
+
compaction: null,
|
|
556
|
+
overflowReduce: null,
|
|
557
|
+
persistence: null,
|
|
558
|
+
titleGenerate: null,
|
|
559
|
+
toolResultTruncate: null,
|
|
560
|
+
emptyResponse: null,
|
|
561
|
+
toolError: null,
|
|
562
|
+
circuitBreaker: null,
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
});
|
|
@@ -59,8 +59,9 @@ afterEach(() => {
|
|
|
59
59
|
// otherwise under ~/.vellum/workspace.
|
|
60
60
|
describe("path characterization", () => {
|
|
61
61
|
test("all path helpers resolve to expected locations", () => {
|
|
62
|
-
// Without VELLUM_WORKSPACE_DIR override,
|
|
62
|
+
// Without VELLUM_WORKSPACE_DIR or BASE_DATA_DIR override, everything is under ~/.vellum
|
|
63
63
|
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
64
|
+
delete process.env.BASE_DATA_DIR;
|
|
64
65
|
const root = join(homedir(), ".vellum");
|
|
65
66
|
const ws = getWorkspaceDir();
|
|
66
67
|
const data = getDataDir();
|
|
@@ -87,6 +88,7 @@ describe("path characterization", () => {
|
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
test("VELLUM_WORKSPACE_DIR overrides workspace location", () => {
|
|
91
|
+
delete process.env.BASE_DATA_DIR;
|
|
90
92
|
process.env.VELLUM_WORKSPACE_DIR = "/tmp/custom-workspace";
|
|
91
93
|
expect(getWorkspaceDir()).toBe("/tmp/custom-workspace");
|
|
92
94
|
expect(getDataDir()).toBe("/tmp/custom-workspace/data");
|
|
@@ -115,6 +117,7 @@ describe("path characterization", () => {
|
|
|
115
117
|
|
|
116
118
|
test("hooks directory is inside the workspace boundary", () => {
|
|
117
119
|
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
120
|
+
delete process.env.BASE_DATA_DIR;
|
|
118
121
|
expect(getWorkspaceHooksDir().startsWith(getWorkspaceDir())).toBe(true);
|
|
119
122
|
});
|
|
120
123
|
|
|
@@ -122,6 +125,7 @@ describe("path characterization", () => {
|
|
|
122
125
|
// Use a temp VELLUM_WORKSPACE_DIR so ensureDataDir writes to a temp dir
|
|
123
126
|
// rather than the real ~/.vellum. Root-level dirs still go to ~/.vellum
|
|
124
127
|
// but we only verify workspace dirs here to avoid side effects.
|
|
128
|
+
delete process.env.BASE_DATA_DIR;
|
|
125
129
|
const wsDir = join(tmpdir(), `platform-test-ws-${Date.now()}`);
|
|
126
130
|
process.env.VELLUM_WORKSPACE_DIR = wsDir;
|
|
127
131
|
|
|
@@ -133,7 +137,6 @@ describe("path characterization", () => {
|
|
|
133
137
|
|
|
134
138
|
// Workspace dirs (in our temp location)
|
|
135
139
|
expect(existsSync(wsDir)).toBe(true);
|
|
136
|
-
expect(existsSync(join(wsDir, "hooks"))).toBe(true);
|
|
137
140
|
expect(existsSync(join(wsDir, "skills"))).toBe(true);
|
|
138
141
|
|
|
139
142
|
// Data sub-dirs under workspace
|