@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,181 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
mock.module("../util/logger.js", () => ({
|
|
4
|
+
getLogger: () =>
|
|
5
|
+
new Proxy({} as Record<string, unknown>, {
|
|
6
|
+
get: () => () => {},
|
|
7
|
+
}),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
mock.module("../config/loader.js", () => ({
|
|
11
|
+
getConfig: () => ({
|
|
12
|
+
ui: {},
|
|
13
|
+
model: "test",
|
|
14
|
+
provider: "test",
|
|
15
|
+
memory: { enabled: false },
|
|
16
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
17
|
+
secretDetection: { enabled: false },
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
addMessage,
|
|
23
|
+
clearStrippedInjectionMetadataForConversation,
|
|
24
|
+
createConversation,
|
|
25
|
+
getMessages,
|
|
26
|
+
} from "../memory/conversation-crud.js";
|
|
27
|
+
import { getDb, initializeDb } from "../memory/db.js";
|
|
28
|
+
|
|
29
|
+
initializeDb();
|
|
30
|
+
|
|
31
|
+
function resetTables(): void {
|
|
32
|
+
const db = getDb();
|
|
33
|
+
db.run("DELETE FROM message_attachments");
|
|
34
|
+
db.run("DELETE FROM attachments");
|
|
35
|
+
db.run("DELETE FROM messages");
|
|
36
|
+
db.run("DELETE FROM conversations");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("clearStrippedInjectionMetadataForConversation", () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
resetTables();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("removes all three stripped-block fields and preserves the rest", async () => {
|
|
45
|
+
const conv = createConversation("Strip metadata test");
|
|
46
|
+
await addMessage(
|
|
47
|
+
conv.id,
|
|
48
|
+
"user",
|
|
49
|
+
"turn 1",
|
|
50
|
+
{
|
|
51
|
+
memoryInjectedBlock: "mem payload",
|
|
52
|
+
turnContextBlock: "<turn_context>\nctx\n</turn_context>",
|
|
53
|
+
workspaceBlock: "<workspace>\nws\n</workspace>",
|
|
54
|
+
nowScratchpadBlock:
|
|
55
|
+
"<NOW.md Always keep this up to date>\nnow body\n</NOW.md>",
|
|
56
|
+
pkbContextBlock: "<knowledge_base>\npkb body\n</knowledge_base>",
|
|
57
|
+
pkbSystemReminderBlock:
|
|
58
|
+
"<system_reminder>\nreminder body\n</system_reminder>",
|
|
59
|
+
},
|
|
60
|
+
{ skipIndexing: true },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
clearStrippedInjectionMetadataForConversation(conv.id);
|
|
64
|
+
|
|
65
|
+
const [row] = getMessages(conv.id);
|
|
66
|
+
const meta = JSON.parse(row.metadata ?? "{}");
|
|
67
|
+
|
|
68
|
+
expect(meta.pkbSystemReminderBlock).toBeUndefined();
|
|
69
|
+
expect(meta.nowScratchpadBlock).toBeUndefined();
|
|
70
|
+
expect(meta.pkbContextBlock).toBeUndefined();
|
|
71
|
+
|
|
72
|
+
// Non-stripped fields must survive — these back blocks that
|
|
73
|
+
// `stripInjectionsForCompaction` intentionally leaves in-memory.
|
|
74
|
+
expect(meta.memoryInjectedBlock).toBe("mem payload");
|
|
75
|
+
expect(meta.turnContextBlock).toBe("<turn_context>\nctx\n</turn_context>");
|
|
76
|
+
expect(meta.workspaceBlock).toBe("<workspace>\nws\n</workspace>");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("is idempotent — re-running is a no-op on already-cleared rows", async () => {
|
|
80
|
+
const conv = createConversation("Idempotent clear");
|
|
81
|
+
await addMessage(
|
|
82
|
+
conv.id,
|
|
83
|
+
"user",
|
|
84
|
+
"turn 1",
|
|
85
|
+
{
|
|
86
|
+
memoryInjectedBlock: "keep me",
|
|
87
|
+
nowScratchpadBlock: "<NOW.md …>body</NOW.md>",
|
|
88
|
+
},
|
|
89
|
+
{ skipIndexing: true },
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
clearStrippedInjectionMetadataForConversation(conv.id);
|
|
93
|
+
clearStrippedInjectionMetadataForConversation(conv.id);
|
|
94
|
+
|
|
95
|
+
const [row] = getMessages(conv.id);
|
|
96
|
+
const meta = JSON.parse(row.metadata ?? "{}");
|
|
97
|
+
expect(meta.nowScratchpadBlock).toBeUndefined();
|
|
98
|
+
expect(meta.memoryInjectedBlock).toBe("keep me");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("only targets user rows — assistant metadata is untouched", async () => {
|
|
102
|
+
const conv = createConversation("Role scoping");
|
|
103
|
+
await addMessage(
|
|
104
|
+
conv.id,
|
|
105
|
+
"user",
|
|
106
|
+
"turn 1",
|
|
107
|
+
{ nowScratchpadBlock: "<NOW.md …>body</NOW.md>" },
|
|
108
|
+
{ skipIndexing: true },
|
|
109
|
+
);
|
|
110
|
+
await addMessage(
|
|
111
|
+
conv.id,
|
|
112
|
+
"assistant",
|
|
113
|
+
"reply",
|
|
114
|
+
// Assistant rows don't carry these blocks in practice, but guard the
|
|
115
|
+
// role filter anyway so an accidental drop of the WHERE clause is
|
|
116
|
+
// surfaced immediately.
|
|
117
|
+
{ nowScratchpadBlock: "should-not-be-cleared" },
|
|
118
|
+
{ skipIndexing: true },
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
clearStrippedInjectionMetadataForConversation(conv.id);
|
|
122
|
+
|
|
123
|
+
const rows = getMessages(conv.id);
|
|
124
|
+
const userMeta = JSON.parse(rows[0].metadata ?? "{}");
|
|
125
|
+
const assistantMeta = JSON.parse(rows[1].metadata ?? "{}");
|
|
126
|
+
expect(userMeta.nowScratchpadBlock).toBeUndefined();
|
|
127
|
+
expect(assistantMeta.nowScratchpadBlock).toBe("should-not-be-cleared");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("post-clear, rehydration does not re-inject NOW.md / knowledge_base", async () => {
|
|
131
|
+
// Reproduces the divergence described in the Codex P1 feedback:
|
|
132
|
+
// stripInjectionsForCompaction removes <NOW.md …> and <knowledge_base>
|
|
133
|
+
// from the in-memory history during compaction. Without this clear,
|
|
134
|
+
// a subsequent loadFromDb would rehydrate those blocks from stale
|
|
135
|
+
// metadata — re-injecting exactly what compaction removed.
|
|
136
|
+
const conv = createConversation("Rehydrate after strip");
|
|
137
|
+
await addMessage(
|
|
138
|
+
conv.id,
|
|
139
|
+
"user",
|
|
140
|
+
"historical turn",
|
|
141
|
+
{
|
|
142
|
+
memoryInjectedBlock: "mem",
|
|
143
|
+
turnContextBlock: "<turn_context>\nctx\n</turn_context>",
|
|
144
|
+
workspaceBlock: "<workspace>\nws\n</workspace>",
|
|
145
|
+
nowScratchpadBlock:
|
|
146
|
+
"<NOW.md Always keep this up to date>\nnow\n</NOW.md>",
|
|
147
|
+
pkbContextBlock: "<knowledge_base>\npkb\n</knowledge_base>",
|
|
148
|
+
pkbSystemReminderBlock: "<system_reminder>\nsr\n</system_reminder>",
|
|
149
|
+
},
|
|
150
|
+
{ skipIndexing: true },
|
|
151
|
+
);
|
|
152
|
+
await addMessage(conv.id, "assistant", "reply", undefined, {
|
|
153
|
+
skipIndexing: true,
|
|
154
|
+
});
|
|
155
|
+
await addMessage(conv.id, "user", "tail turn", undefined, {
|
|
156
|
+
skipIndexing: true,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Simulate the compaction-strip lifecycle point.
|
|
160
|
+
clearStrippedInjectionMetadataForConversation(conv.id);
|
|
161
|
+
|
|
162
|
+
const rows = getMessages(conv.id);
|
|
163
|
+
const historicalMeta = JSON.parse(rows[0].metadata ?? "{}");
|
|
164
|
+
|
|
165
|
+
// Loading this back with loadFromDb prepends fields only when they
|
|
166
|
+
// are present on the row. Confirm the stripped fields are gone so
|
|
167
|
+
// rehydration cannot resurrect them.
|
|
168
|
+
expect(historicalMeta.nowScratchpadBlock).toBeUndefined();
|
|
169
|
+
expect(historicalMeta.pkbContextBlock).toBeUndefined();
|
|
170
|
+
expect(historicalMeta.pkbSystemReminderBlock).toBeUndefined();
|
|
171
|
+
|
|
172
|
+
// And the fields that back blocks `stripInjectionsForCompaction`
|
|
173
|
+
// intentionally preserves (<turn_context>, <workspace>, <memory __injected>)
|
|
174
|
+
// must still be present so the cache prefix remains stable.
|
|
175
|
+
expect(historicalMeta.turnContextBlock).toBe(
|
|
176
|
+
"<turn_context>\nctx\n</turn_context>",
|
|
177
|
+
);
|
|
178
|
+
expect(historicalMeta.workspaceBlock).toBe("<workspace>\nws\n</workspace>");
|
|
179
|
+
expect(historicalMeta.memoryInjectedBlock).toBe("mem");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the compaction call-site recovery path added in JARVIS-587.
|
|
3
|
+
*
|
|
4
|
+
* When the `compaction` pipeline exceeds its 30s budget (manifest-wide
|
|
5
|
+
* `DEFAULT_TIMEOUTS.compaction`), the pipeline runner throws
|
|
6
|
+
* `PluginTimeoutError`. The three compaction call sites in
|
|
7
|
+
* `conversation-agent-loop.ts` (start-of-turn, mid-loop, emergency) catch
|
|
8
|
+
* that error, invoke `trackCompactionOutcome(..., true, onEvent)` so the
|
|
9
|
+
* circuit breaker records the failure, and degrade gracefully.
|
|
10
|
+
*
|
|
11
|
+
* This file asserts the tight coupling between:
|
|
12
|
+
* (1) a `PluginTimeoutError`-driven failure and
|
|
13
|
+
* (2) the compaction circuit breaker's 3-strike threshold.
|
|
14
|
+
*
|
|
15
|
+
* The existing `circuit-breaker-pipeline.test.ts` already exercises the
|
|
16
|
+
* breaker's transitions end-to-end. These tests verify that our new
|
|
17
|
+
* catch-blocks feed the breaker the same `"failure"` outcome a normal
|
|
18
|
+
* summary-LLM throw would, and that repeated timeouts therefore trip the
|
|
19
|
+
* same 3-strike trip.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
23
|
+
|
|
24
|
+
import { trackCompactionOutcome } from "../daemon/conversation-agent-loop.js";
|
|
25
|
+
import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
|
|
26
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
27
|
+
import {
|
|
28
|
+
COMPACTION_CIRCUIT_FAILURE_THRESHOLD,
|
|
29
|
+
defaultCircuitBreakerPlugin,
|
|
30
|
+
} from "../plugins/defaults/circuit-breaker.js";
|
|
31
|
+
import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
|
|
32
|
+
import {
|
|
33
|
+
getMiddlewaresFor,
|
|
34
|
+
registerPlugin,
|
|
35
|
+
resetPluginRegistryForTests,
|
|
36
|
+
} from "../plugins/registry.js";
|
|
37
|
+
import {
|
|
38
|
+
type CompactionArgs,
|
|
39
|
+
type CompactionResult,
|
|
40
|
+
type Middleware,
|
|
41
|
+
PluginTimeoutError,
|
|
42
|
+
type TurnContext,
|
|
43
|
+
} from "../plugins/types.js";
|
|
44
|
+
|
|
45
|
+
// ─── Fixtures ───────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
interface FakeConversationCtx {
|
|
48
|
+
readonly conversationId: string;
|
|
49
|
+
consecutiveCompactionFailures: number;
|
|
50
|
+
compactionCircuitOpenUntil: number | null;
|
|
51
|
+
currentRequestId?: string;
|
|
52
|
+
currentTurnTrustContext?: TrustContext;
|
|
53
|
+
trustContext?: TrustContext;
|
|
54
|
+
turnCount: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function makeConversationCtx(
|
|
58
|
+
conversationId = "conv-timeout-test",
|
|
59
|
+
): FakeConversationCtx {
|
|
60
|
+
return {
|
|
61
|
+
conversationId,
|
|
62
|
+
consecutiveCompactionFailures: 0,
|
|
63
|
+
compactionCircuitOpenUntil: null,
|
|
64
|
+
turnCount: 0,
|
|
65
|
+
trustContext: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const trust: TrustContext = {
|
|
70
|
+
sourceChannel: "vellum",
|
|
71
|
+
trustClass: "guardian",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function makeTurnCtx(conversationId: string): TurnContext {
|
|
75
|
+
return {
|
|
76
|
+
requestId: "req-timeout-test",
|
|
77
|
+
conversationId,
|
|
78
|
+
turnIndex: 0,
|
|
79
|
+
trust,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function collectEvents(): {
|
|
84
|
+
events: ServerMessage[];
|
|
85
|
+
onEvent: (msg: ServerMessage) => void;
|
|
86
|
+
} {
|
|
87
|
+
const events: ServerMessage[] = [];
|
|
88
|
+
return { events, onEvent: (msg) => events.push(msg) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── Tests ──────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
describe("compaction timeout recovery (JARVIS-587)", () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
resetPluginRegistryForTests();
|
|
96
|
+
registerPlugin(defaultCircuitBreakerPlugin);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
resetPluginRegistryForTests();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("runPipeline('compaction', ...) throws PluginTimeoutError on budget breach", async () => {
|
|
104
|
+
// Baseline: the compaction pipeline still surfaces PluginTimeoutError when
|
|
105
|
+
// its timer fires. This guards the outer race from silently swallowing
|
|
106
|
+
// the timeout when the inner call is aborted by our Part A wiring.
|
|
107
|
+
const hang: Middleware<CompactionArgs, CompactionResult> = async (
|
|
108
|
+
_args,
|
|
109
|
+
_next,
|
|
110
|
+
) =>
|
|
111
|
+
new Promise<CompactionResult>(() => {
|
|
112
|
+
// intentionally never resolves
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let caught: unknown;
|
|
116
|
+
try {
|
|
117
|
+
await runPipeline<CompactionArgs, CompactionResult>(
|
|
118
|
+
"compaction",
|
|
119
|
+
[hang],
|
|
120
|
+
async () => ({ compacted: false }) as unknown as CompactionResult,
|
|
121
|
+
{ messages: [] as unknown, signal: undefined, options: undefined },
|
|
122
|
+
makeTurnCtx("conv-budget-breach"),
|
|
123
|
+
// Override the manifest timeout to keep the test fast.
|
|
124
|
+
20,
|
|
125
|
+
);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
caught = err;
|
|
128
|
+
}
|
|
129
|
+
expect(caught).toBeInstanceOf(PluginTimeoutError);
|
|
130
|
+
expect((caught as PluginTimeoutError).pipeline).toBe("compaction");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("trackCompactionOutcome(failed=true) driven by PluginTimeoutError trips the breaker at the 3rd strike", async () => {
|
|
134
|
+
// Simulates the production sequence: each mid-loop compaction hits the
|
|
135
|
+
// pipeline's 30s ceiling, the orchestrator's catch block calls
|
|
136
|
+
// `trackCompactionOutcome(ctx, true, onEvent)`. After three such catches
|
|
137
|
+
// the circuit breaker must be open — matching the same invariant the
|
|
138
|
+
// existing breaker test file exercises for normal summary-LLM throws.
|
|
139
|
+
const ctx = makeConversationCtx();
|
|
140
|
+
const { onEvent, events } = collectEvents();
|
|
141
|
+
|
|
142
|
+
// First two timeouts — circuit still closed.
|
|
143
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
144
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
145
|
+
expect(ctx.consecutiveCompactionFailures).toBe(2);
|
|
146
|
+
expect(ctx.compactionCircuitOpenUntil).toBeNull();
|
|
147
|
+
expect(events).toHaveLength(0);
|
|
148
|
+
|
|
149
|
+
// Third timeout — breaker trips and emits the canonical transition event.
|
|
150
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
151
|
+
expect(ctx.consecutiveCompactionFailures).toBe(
|
|
152
|
+
COMPACTION_CIRCUIT_FAILURE_THRESHOLD,
|
|
153
|
+
);
|
|
154
|
+
expect(ctx.compactionCircuitOpenUntil).not.toBeNull();
|
|
155
|
+
expect(events).toHaveLength(1);
|
|
156
|
+
expect(events[0]).toEqual({
|
|
157
|
+
type: "compaction_circuit_open",
|
|
158
|
+
conversationId: ctx.conversationId,
|
|
159
|
+
reason: "3_consecutive_failures",
|
|
160
|
+
openUntil: ctx.compactionCircuitOpenUntil as number,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("a successful compaction after two timeouts resets the counter", async () => {
|
|
165
|
+
// The recovery path doesn't interfere with the breaker's normal reset —
|
|
166
|
+
// once a compaction call eventually succeeds, the streak is broken and
|
|
167
|
+
// the next failure starts counting from 1.
|
|
168
|
+
const ctx = makeConversationCtx();
|
|
169
|
+
const { onEvent } = collectEvents();
|
|
170
|
+
|
|
171
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
172
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
173
|
+
expect(ctx.consecutiveCompactionFailures).toBe(2);
|
|
174
|
+
|
|
175
|
+
await trackCompactionOutcome(ctx, false, onEvent);
|
|
176
|
+
expect(ctx.consecutiveCompactionFailures).toBe(0);
|
|
177
|
+
expect(ctx.compactionCircuitOpenUntil).toBeNull();
|
|
178
|
+
|
|
179
|
+
await trackCompactionOutcome(ctx, true, onEvent);
|
|
180
|
+
expect(ctx.consecutiveCompactionFailures).toBe(1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("compaction call-site recovery remains defense-in-depth even when DEFAULT_TIMEOUTS.compaction is null", () => {
|
|
184
|
+
// At the time this PR landed, `DEFAULT_TIMEOUTS.compaction` was null
|
|
185
|
+
// (pipeline timeouts globally disabled — see #27608). That makes the
|
|
186
|
+
// call-site catch blocks unreachable in production right now, but the
|
|
187
|
+
// catch blocks still matter: any future reintroduction of a per-pipeline
|
|
188
|
+
// compaction timeout immediately benefits from circuit-breaker recording
|
|
189
|
+
// and graceful-degradation without needing to re-touch every call site.
|
|
190
|
+
//
|
|
191
|
+
// This test just documents the current value so a future change that
|
|
192
|
+
// reintroduces a timeout must also decide (intentionally) whether the
|
|
193
|
+
// recovery path should continue to fire.
|
|
194
|
+
const value = DEFAULT_TIMEOUTS.compaction;
|
|
195
|
+
expect(value === null || typeof value === "number").toBe(true);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("abort propagation end-to-end (Part A + updateSummary fallback)", () => {
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
resetPluginRegistryForTests();
|
|
202
|
+
registerPlugin(defaultCircuitBreakerPlugin);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
afterEach(() => {
|
|
206
|
+
resetPluginRegistryForTests();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("caller-provided signal is replaced with a linked signal that fires on timeout", async () => {
|
|
210
|
+
// Minimal proof that Part A actually wires the signal through the
|
|
211
|
+
// compaction pipeline: when the pipeline runner's timer fires, the
|
|
212
|
+
// signal seen by the inner terminal is aborted — allowing
|
|
213
|
+
// `updateSummary`'s try/catch around `provider.sendMessage` to trigger
|
|
214
|
+
// the local-fallback path instead of the call hanging indefinitely.
|
|
215
|
+
let observedSignal: AbortSignal | undefined;
|
|
216
|
+
const waitForAbort: Middleware<CompactionArgs, CompactionResult> = async (
|
|
217
|
+
args,
|
|
218
|
+
_next,
|
|
219
|
+
) => {
|
|
220
|
+
observedSignal = args.signal;
|
|
221
|
+
return new Promise<CompactionResult>((_resolve, reject) => {
|
|
222
|
+
args.signal?.addEventListener("abort", () => {
|
|
223
|
+
reject(
|
|
224
|
+
Object.assign(new Error("aborted by signal"), {
|
|
225
|
+
name: "AbortError",
|
|
226
|
+
}),
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const callerController = new AbortController();
|
|
233
|
+
|
|
234
|
+
let caught: unknown;
|
|
235
|
+
try {
|
|
236
|
+
await runPipeline<CompactionArgs, CompactionResult>(
|
|
237
|
+
"compaction",
|
|
238
|
+
[waitForAbort, ...getMiddlewaresFor("compaction")],
|
|
239
|
+
async () => ({ compacted: false }) as unknown as CompactionResult,
|
|
240
|
+
{
|
|
241
|
+
messages: [] as unknown,
|
|
242
|
+
signal: callerController.signal,
|
|
243
|
+
options: undefined,
|
|
244
|
+
},
|
|
245
|
+
makeTurnCtx("conv-abort-test"),
|
|
246
|
+
20,
|
|
247
|
+
);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
caught = err;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
expect(caught).toBeInstanceOf(PluginTimeoutError);
|
|
253
|
+
expect(observedSignal).toBeDefined();
|
|
254
|
+
// The runner swaps the caller's signal for a linked signal — the two are
|
|
255
|
+
// distinct objects but both should end up aborted once the timer fires.
|
|
256
|
+
expect(observedSignal).not.toBe(callerController.signal);
|
|
257
|
+
expect(observedSignal!.aborted).toBe(true);
|
|
258
|
+
// Caller's own signal is untouched — pipeline timeout does not cascade
|
|
259
|
+
// outward onto the caller's controller.
|
|
260
|
+
expect(callerController.signal.aborted).toBe(false);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
+
|
|
5
|
+
import { invalidateConfigCache } from "../config/loader.js";
|
|
6
|
+
import {
|
|
7
|
+
type ModelSetContext,
|
|
8
|
+
setImageGenModel,
|
|
9
|
+
} from "../daemon/handlers/config-model.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Helpers
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
const WORKSPACE_DIR = process.env.VELLUM_WORKSPACE_DIR!;
|
|
16
|
+
const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
|
|
17
|
+
|
|
18
|
+
function ensureTestDir(): void {
|
|
19
|
+
const dirs = [WORKSPACE_DIR, join(WORKSPACE_DIR, "data")];
|
|
20
|
+
for (const dir of dirs) {
|
|
21
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeConfig(obj: unknown): void {
|
|
26
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(obj));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readConfig(): Record<string, unknown> {
|
|
30
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function makeCtx(): ModelSetContext {
|
|
34
|
+
return {
|
|
35
|
+
conversations: new Map(),
|
|
36
|
+
suppressConfigReload: false,
|
|
37
|
+
setSuppressConfigReload: () => {},
|
|
38
|
+
updateConfigFingerprint: () => {},
|
|
39
|
+
debounceTimers: {
|
|
40
|
+
// No-op scheduler: the test asserts the persisted config shape, not
|
|
41
|
+
// the debounce behaviour. Firing the callback synchronously would
|
|
42
|
+
// mutate state after the assertion; dropping it keeps the test
|
|
43
|
+
// deterministic.
|
|
44
|
+
schedule: (_key: string, _fn: () => void, _ms: number) => {},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Setup / Teardown
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
ensureTestDir();
|
|
55
|
+
writeConfig({});
|
|
56
|
+
invalidateConfigCache();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
try {
|
|
61
|
+
writeConfig({});
|
|
62
|
+
invalidateConfigCache();
|
|
63
|
+
} catch {
|
|
64
|
+
// best-effort cleanup
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Tests
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
describe("setImageGenModel — provider derived from model prefix", () => {
|
|
73
|
+
test("gemini model writes provider=gemini", () => {
|
|
74
|
+
setImageGenModel("gemini-3.1-flash-image-preview", makeCtx());
|
|
75
|
+
|
|
76
|
+
const config = readConfig();
|
|
77
|
+
const imageGen = (config.services as any)?.["image-generation"];
|
|
78
|
+
expect(imageGen?.model).toBe("gemini-3.1-flash-image-preview");
|
|
79
|
+
expect(imageGen?.provider).toBe("gemini");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("gpt-image-2 writes provider=openai", () => {
|
|
83
|
+
setImageGenModel("gpt-image-2", makeCtx());
|
|
84
|
+
|
|
85
|
+
const config = readConfig();
|
|
86
|
+
const imageGen = (config.services as any)?.["image-generation"];
|
|
87
|
+
expect(imageGen?.model).toBe("gpt-image-2");
|
|
88
|
+
expect(imageGen?.provider).toBe("openai");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("dall-e-3 writes provider=openai", () => {
|
|
92
|
+
setImageGenModel("dall-e-3", makeCtx());
|
|
93
|
+
|
|
94
|
+
const config = readConfig();
|
|
95
|
+
const imageGen = (config.services as any)?.["image-generation"];
|
|
96
|
+
expect(imageGen?.model).toBe("dall-e-3");
|
|
97
|
+
expect(imageGen?.provider).toBe("openai");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("switching from gemini to openai flips provider in place", () => {
|
|
101
|
+
setImageGenModel("gemini-3.1-flash-image-preview", makeCtx());
|
|
102
|
+
let imageGen = (readConfig().services as any)?.["image-generation"];
|
|
103
|
+
expect(imageGen?.provider).toBe("gemini");
|
|
104
|
+
|
|
105
|
+
setImageGenModel("gpt-image-2", makeCtx());
|
|
106
|
+
imageGen = (readConfig().services as any)?.["image-generation"];
|
|
107
|
+
expect(imageGen?.model).toBe("gpt-image-2");
|
|
108
|
+
expect(imageGen?.provider).toBe("openai");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -85,7 +85,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
85
85
|
// llm.default.{provider,model} (see PR 19 of unify-llm-callsites).
|
|
86
86
|
expect(result.services.inference.mode).toBe("your-own");
|
|
87
87
|
expect(result.llm.default.provider).toBe("anthropic");
|
|
88
|
-
expect(result.llm.default.model).toBe("claude-
|
|
88
|
+
expect(result.llm.default.model).toBe("claude-sonnet-4-6");
|
|
89
89
|
expect(result.services["image-generation"].provider).toBe("gemini");
|
|
90
90
|
expect(result.services["image-generation"].model).toBe(
|
|
91
91
|
"gemini-3.1-flash-image-preview",
|
|
@@ -170,7 +170,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
170
170
|
expect(result.llm).toBeDefined();
|
|
171
171
|
expect(result.llm.default).toEqual({
|
|
172
172
|
provider: "anthropic",
|
|
173
|
-
model: "claude-
|
|
173
|
+
model: "claude-sonnet-4-6",
|
|
174
174
|
maxTokens: 64000,
|
|
175
175
|
effort: "max",
|
|
176
176
|
speed: "standard",
|
|
@@ -305,7 +305,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
305
305
|
(result.services.inference as Record<string, unknown>).model,
|
|
306
306
|
).toBeUndefined();
|
|
307
307
|
expect(result.llm.default.provider).toBe("anthropic");
|
|
308
|
-
expect(result.llm.default.model).toBe("claude-
|
|
308
|
+
expect(result.llm.default.model).toBe("claude-sonnet-4-6");
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
test("partial llm config (empty `llm: {}`) doesn't trigger full config reset", () => {
|
|
@@ -320,7 +320,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
320
320
|
});
|
|
321
321
|
expect(result.llm.default.maxTokens).toBe(32000);
|
|
322
322
|
expect(result.llm.default.provider).toBe("anthropic");
|
|
323
|
-
expect(result.llm.default.model).toBe("claude-
|
|
323
|
+
expect(result.llm.default.model).toBe("claude-sonnet-4-6");
|
|
324
324
|
});
|
|
325
325
|
|
|
326
326
|
test("llm.default with one missing field still parses (defaults applied)", () => {
|
|
@@ -748,11 +748,11 @@ describe("AssistantConfigSchema", () => {
|
|
|
748
748
|
expect(result.permissions.autoApproveUpTo).toBe("medium");
|
|
749
749
|
});
|
|
750
750
|
|
|
751
|
-
test("
|
|
752
|
-
const result = AssistantConfigSchema.
|
|
751
|
+
test("accepts autoApproveUpTo high", () => {
|
|
752
|
+
const result = AssistantConfigSchema.parse({
|
|
753
753
|
permissions: { autoApproveUpTo: "high" },
|
|
754
754
|
});
|
|
755
|
-
expect(result.
|
|
755
|
+
expect(result.permissions.autoApproveUpTo).toBe("high");
|
|
756
756
|
});
|
|
757
757
|
|
|
758
758
|
test("rejects invalid autoApproveUpTo string", () => {
|
|
@@ -804,12 +804,25 @@ describe("AssistantConfigSchema", () => {
|
|
|
804
804
|
test("per-context object rejects invalid enum values", () => {
|
|
805
805
|
const result = AssistantConfigSchema.safeParse({
|
|
806
806
|
permissions: {
|
|
807
|
-
autoApproveUpTo: { conversation: "
|
|
807
|
+
autoApproveUpTo: { conversation: "extreme" },
|
|
808
808
|
},
|
|
809
809
|
});
|
|
810
810
|
expect(result.success).toBe(false);
|
|
811
811
|
});
|
|
812
812
|
|
|
813
|
+
test("per-context object accepts high enum value", () => {
|
|
814
|
+
const result = AssistantConfigSchema.parse({
|
|
815
|
+
permissions: {
|
|
816
|
+
autoApproveUpTo: { conversation: "high" },
|
|
817
|
+
},
|
|
818
|
+
});
|
|
819
|
+
expect(result.permissions.autoApproveUpTo).toEqual({
|
|
820
|
+
conversation: "high",
|
|
821
|
+
background: "medium",
|
|
822
|
+
headless: "none",
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
|
|
813
826
|
test("per-context object round-trips through JSON serialization", () => {
|
|
814
827
|
const original = AssistantConfigSchema.parse({
|
|
815
828
|
permissions: {
|
|
@@ -2254,7 +2267,7 @@ describe("loadConfig with schema validation", () => {
|
|
|
2254
2267
|
writeConfig({});
|
|
2255
2268
|
const config = loadConfig();
|
|
2256
2269
|
expect(config.llm.default.provider).toBe("anthropic");
|
|
2257
|
-
expect(config.llm.default.model).toBe("claude-
|
|
2270
|
+
expect(config.llm.default.model).toBe("claude-sonnet-4-6");
|
|
2258
2271
|
expect(config.llm.default.maxTokens).toBe(64000);
|
|
2259
2272
|
expect(config.llm.default.thinking).toEqual({
|
|
2260
2273
|
enabled: true,
|
|
@@ -219,10 +219,6 @@ mock.module("../signals/mcp-reload.js", () => ({
|
|
|
219
219
|
handleMcpReloadSignal: () => {},
|
|
220
220
|
}));
|
|
221
221
|
|
|
222
|
-
mock.module("../signals/shotgun.js", () => ({
|
|
223
|
-
handleShotgunSignal: () => {},
|
|
224
|
-
}));
|
|
225
|
-
|
|
226
222
|
mock.module("../signals/user-message.js", () => ({
|
|
227
223
|
handleUserMessageSignal: () => {},
|
|
228
224
|
}));
|
|
@@ -37,6 +37,32 @@ mock.module("../runtime/auth/token-service.js", () => ({
|
|
|
37
37
|
mintEdgeRelayToken: () => "test-token",
|
|
38
38
|
}));
|
|
39
39
|
|
|
40
|
+
// contact-search now calls cliIpcCall instead of the gateway HTTP.
|
|
41
|
+
// Mock the IPC client to dispatch search_contacts to the real store
|
|
42
|
+
// (backed by the test DB) without needing a running IPC server.
|
|
43
|
+
mock.module("../ipc/cli-client.js", () => ({
|
|
44
|
+
cliIpcCall: async (method: string, params?: Record<string, unknown>) => {
|
|
45
|
+
const store = await import("../contacts/contact-store.js");
|
|
46
|
+
if (method === "search_contacts") {
|
|
47
|
+
return { ok: true, result: store.searchContacts(params ?? {}) };
|
|
48
|
+
}
|
|
49
|
+
if (method === "upsert_contact") {
|
|
50
|
+
return { ok: true, result: store.upsertContact(params as never) };
|
|
51
|
+
}
|
|
52
|
+
if (method === "get_contact") {
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
result: store.getContact((params as { id: string }).id) ?? null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (method === "merge_contacts") {
|
|
59
|
+
const { keepId, mergeId } = params as { keepId: string; mergeId: string };
|
|
60
|
+
return { ok: true, result: store.mergeContacts(keepId, mergeId) };
|
|
61
|
+
}
|
|
62
|
+
return { ok: false, error: `Unknown IPC method: ${method}` };
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
|
|
40
66
|
import type { Database } from "bun:sqlite";
|
|
41
67
|
|
|
42
68
|
import { executeContactMerge } from "../config/bundled-skills/contacts/tools/contact-merge.js";
|