@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
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
CheckpointInfo,
|
|
7
7
|
} from "../agent/loop.js";
|
|
8
8
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
9
|
+
import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
|
|
9
10
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
10
11
|
|
|
11
12
|
// ── Module mocks (must precede imports of the module under test) ─────
|
|
@@ -57,10 +58,19 @@ mock.module("../config/loader.js", () => ({
|
|
|
57
58
|
// ── Overflow recovery mocks ──────────────────────────────────────────
|
|
58
59
|
|
|
59
60
|
// Token estimator returns a small value by default (well within budget)
|
|
60
|
-
// so preflight does not trigger unless the test overrides it.
|
|
61
|
+
// so preflight does not trigger unless the test overrides it. Both the
|
|
62
|
+
// calibrated entry point (`estimatePromptTokens`, used in the convergence
|
|
63
|
+
// path) and the raw entry point (`estimatePromptTokensRaw`, used by the
|
|
64
|
+
// default `tokenEstimate` plugin pipeline for preflight/mid-loop) are
|
|
65
|
+
// stubbed so either call site can drive the test.
|
|
61
66
|
let mockEstimateTokens = 1000;
|
|
62
67
|
mock.module("../context/token-estimator.js", () => ({
|
|
63
68
|
estimatePromptTokens: () => mockEstimateTokens,
|
|
69
|
+
estimatePromptTokensRaw: () => mockEstimateTokens,
|
|
70
|
+
// Pass-through: the default plugin computes `toolTokenBudget` via this
|
|
71
|
+
// helper before delegating to the raw estimator. Return 0 so the mocked
|
|
72
|
+
// raw estimate is not perturbed.
|
|
73
|
+
estimateToolsTokens: () => 0,
|
|
64
74
|
}));
|
|
65
75
|
|
|
66
76
|
// Reducer: by default returns the input untouched and marks exhausted
|
|
@@ -103,31 +113,10 @@ mock.module("../daemon/context-overflow-policy.js", () => ({
|
|
|
103
113
|
resolveOverflowAction: () => mockOverflowAction,
|
|
104
114
|
}));
|
|
105
115
|
|
|
106
|
-
// Approval: default to denied
|
|
107
|
-
let mockApprovalResult = { approved: false };
|
|
108
|
-
mock.module("../daemon/context-overflow-approval.js", () => ({
|
|
109
|
-
requestCompressionApproval: async () => mockApprovalResult,
|
|
110
|
-
CONTEXT_OVERFLOW_TOOL_NAME: "context_overflow_compression",
|
|
111
|
-
}));
|
|
112
|
-
|
|
113
|
-
let hookBlocked = false;
|
|
114
|
-
let hookBlockedBy = "";
|
|
115
|
-
|
|
116
|
-
mock.module("../hooks/manager.js", () => ({
|
|
117
|
-
getHookManager: () => ({
|
|
118
|
-
trigger: async (hookName: string) => {
|
|
119
|
-
if (hookName === "pre-message" && hookBlocked) {
|
|
120
|
-
return { blocked: true, blockedBy: hookBlockedBy };
|
|
121
|
-
}
|
|
122
|
-
return { blocked: false };
|
|
123
|
-
},
|
|
124
|
-
}),
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
116
|
const updateMessageMetadataMock = mock(
|
|
128
117
|
(_id: string, _updates: Record<string, unknown>) => {},
|
|
129
118
|
);
|
|
130
|
-
const
|
|
119
|
+
const clearStrippedInjectionMetadataForConversationMock = mock(
|
|
131
120
|
(_conversationId: string) => {},
|
|
132
121
|
);
|
|
133
122
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
@@ -135,8 +124,8 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
135
124
|
setConversationOriginChannelIfUnset: () => {},
|
|
136
125
|
updateConversationUsage: () => {},
|
|
137
126
|
updateMessageMetadata: updateMessageMetadataMock,
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
clearStrippedInjectionMetadataForConversation:
|
|
128
|
+
clearStrippedInjectionMetadataForConversationMock,
|
|
140
129
|
getMessages: () => [],
|
|
141
130
|
getConversation: () => ({
|
|
142
131
|
id: "conv-1",
|
|
@@ -374,7 +363,9 @@ type AgentLoopRun = (
|
|
|
374
363
|
onEvent: (event: AgentEvent) => void,
|
|
375
364
|
signal?: AbortSignal,
|
|
376
365
|
requestId?: string,
|
|
377
|
-
onCheckpoint?: (
|
|
366
|
+
onCheckpoint?: (
|
|
367
|
+
checkpoint: CheckpointInfo,
|
|
368
|
+
) => CheckpointDecision | Promise<CheckpointDecision>,
|
|
378
369
|
) => Promise<Message[]>;
|
|
379
370
|
|
|
380
371
|
function makeCtx(
|
|
@@ -404,6 +395,7 @@ function makeCtx(
|
|
|
404
395
|
agentLoop: {
|
|
405
396
|
run: agentLoopRun,
|
|
406
397
|
getToolTokenBudget: () => 0,
|
|
398
|
+
getResolvedTools: () => [],
|
|
407
399
|
// Tests here don't exercise calibration; returning undefined makes
|
|
408
400
|
// the estimator use the per-provider aggregate key.
|
|
409
401
|
getActiveModel: () => undefined,
|
|
@@ -509,12 +501,9 @@ function makeCtx(
|
|
|
509
501
|
// ── Tests ────────────────────────────────────────────────────────────
|
|
510
502
|
|
|
511
503
|
beforeEach(() => {
|
|
512
|
-
hookBlocked = false;
|
|
513
|
-
hookBlockedBy = "";
|
|
514
504
|
mockEstimateTokens = 1000;
|
|
515
505
|
mockReducerStepFn = null;
|
|
516
506
|
mockOverflowAction = "fail_gracefully";
|
|
517
|
-
mockApprovalResult = { approved: false };
|
|
518
507
|
mockInjectionBlocks = {};
|
|
519
508
|
recordUsageMock.mockClear();
|
|
520
509
|
recordRequestLogMock.mockClear();
|
|
@@ -522,11 +511,17 @@ beforeEach(() => {
|
|
|
522
511
|
rebuildConversationDiskViewFromDbStateMock.mockClear();
|
|
523
512
|
updateMessageMetadataMock.mockClear();
|
|
524
513
|
updateMessageMetadataMock.mockImplementation(() => {});
|
|
525
|
-
|
|
526
|
-
|
|
514
|
+
clearStrippedInjectionMetadataForConversationMock.mockClear();
|
|
515
|
+
clearStrippedInjectionMetadataForConversationMock.mockImplementation(
|
|
527
516
|
() => {},
|
|
528
517
|
);
|
|
529
518
|
applyRuntimeInjectionsMock.mockClear();
|
|
519
|
+
// Orchestrator pipelines (overflowReduce, persistence, …) run through the
|
|
520
|
+
// plugin registry; reset and re-register every default so the pipelines
|
|
521
|
+
// dispatch to middleware backed by the mocked collaborators these tests
|
|
522
|
+
// install (`reduceContextOverflow`, `syncMessageToDisk`, etc.) instead of
|
|
523
|
+
// hitting the bare terminals.
|
|
524
|
+
resetPluginRegistryAndRegisterDefaults();
|
|
530
525
|
});
|
|
531
526
|
|
|
532
527
|
describe("session-agent-loop", () => {
|
|
@@ -540,47 +535,6 @@ describe("session-agent-loop", () => {
|
|
|
540
535
|
});
|
|
541
536
|
});
|
|
542
537
|
|
|
543
|
-
describe("pre-message hook blocking", () => {
|
|
544
|
-
test("emits error and returns early when pre-message hook blocks", async () => {
|
|
545
|
-
hookBlocked = true;
|
|
546
|
-
hookBlockedBy = "test-hook";
|
|
547
|
-
const events: ServerMessage[] = [];
|
|
548
|
-
const ctx = makeCtx();
|
|
549
|
-
|
|
550
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
551
|
-
|
|
552
|
-
const errorEvent = events.find((e) => e.type === "error");
|
|
553
|
-
expect(errorEvent).toBeDefined();
|
|
554
|
-
expect((errorEvent as { message: string }).message).toContain(
|
|
555
|
-
"test-hook",
|
|
556
|
-
);
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
test("removes user message when hook blocks without skipPreMessageRollback", async () => {
|
|
560
|
-
hookBlocked = true;
|
|
561
|
-
hookBlockedBy = "guard";
|
|
562
|
-
const ctx = makeCtx();
|
|
563
|
-
const originalLength = ctx.messages.length;
|
|
564
|
-
|
|
565
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
|
|
566
|
-
|
|
567
|
-
expect(ctx.messages.length).toBe(originalLength - 1);
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
test("keeps user message when hook blocks with skipPreMessageRollback", async () => {
|
|
571
|
-
hookBlocked = true;
|
|
572
|
-
hookBlockedBy = "guard";
|
|
573
|
-
const ctx = makeCtx();
|
|
574
|
-
const originalLength = ctx.messages.length;
|
|
575
|
-
|
|
576
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", () => {}, {
|
|
577
|
-
skipPreMessageRollback: true,
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
expect(ctx.messages.length).toBe(originalLength);
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
|
|
584
538
|
describe("tool execution errors via agent loop", () => {
|
|
585
539
|
test("error events from agent loop are classified and emitted", async () => {
|
|
586
540
|
const events: ServerMessage[] = [];
|
|
@@ -1308,71 +1262,6 @@ describe("session-agent-loop", () => {
|
|
|
1308
1262
|
expect(complete).toBeDefined();
|
|
1309
1263
|
});
|
|
1310
1264
|
|
|
1311
|
-
test("interactive deny produces graceful assistant response instead of conversation_error", async () => {
|
|
1312
|
-
const events: ServerMessage[] = [];
|
|
1313
|
-
|
|
1314
|
-
// Reducer exhausts all tiers but context is still too large
|
|
1315
|
-
mockReducerStepFn = (msgs: Message[]) => ({
|
|
1316
|
-
messages: msgs,
|
|
1317
|
-
tier: "injection_downgrade",
|
|
1318
|
-
state: {
|
|
1319
|
-
appliedTiers: [
|
|
1320
|
-
"forced_compaction",
|
|
1321
|
-
"tool_result_truncation",
|
|
1322
|
-
"media_stubbing",
|
|
1323
|
-
"injection_downgrade",
|
|
1324
|
-
],
|
|
1325
|
-
injectionMode: "minimal",
|
|
1326
|
-
exhausted: true,
|
|
1327
|
-
},
|
|
1328
|
-
estimatedTokens: 120000,
|
|
1329
|
-
});
|
|
1330
|
-
|
|
1331
|
-
mockOverflowAction = "request_user_approval";
|
|
1332
|
-
mockApprovalResult = { approved: false };
|
|
1333
|
-
|
|
1334
|
-
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
1335
|
-
onEvent({
|
|
1336
|
-
type: "error",
|
|
1337
|
-
error: new Error("context_length_exceeded"),
|
|
1338
|
-
});
|
|
1339
|
-
onEvent({
|
|
1340
|
-
type: "usage",
|
|
1341
|
-
inputTokens: 100,
|
|
1342
|
-
outputTokens: 0,
|
|
1343
|
-
model: "test-model",
|
|
1344
|
-
providerDurationMs: 50,
|
|
1345
|
-
});
|
|
1346
|
-
return messages;
|
|
1347
|
-
};
|
|
1348
|
-
|
|
1349
|
-
const ctx = makeCtx({
|
|
1350
|
-
agentLoopRun,
|
|
1351
|
-
contextWindowManager: {
|
|
1352
|
-
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
1353
|
-
maybeCompact: async () => ({ compacted: false }),
|
|
1354
|
-
} as unknown as AgentLoopConversationContext["contextWindowManager"],
|
|
1355
|
-
});
|
|
1356
|
-
|
|
1357
|
-
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
1358
|
-
|
|
1359
|
-
// Should NOT emit conversation_error
|
|
1360
|
-
const conversationError = events.find(
|
|
1361
|
-
(e) => e.type === "conversation_error",
|
|
1362
|
-
);
|
|
1363
|
-
expect(conversationError).toBeUndefined();
|
|
1364
|
-
|
|
1365
|
-
// Should emit a graceful assistant text delta instead
|
|
1366
|
-
const textDeltas = events.filter(
|
|
1367
|
-
(e) => e.type === "assistant_text_delta",
|
|
1368
|
-
);
|
|
1369
|
-
expect(textDeltas.length).toBeGreaterThanOrEqual(1);
|
|
1370
|
-
const lastDelta = textDeltas[textDeltas.length - 1] as {
|
|
1371
|
-
text: string;
|
|
1372
|
-
};
|
|
1373
|
-
expect(lastDelta.text).toContain("compression was declined");
|
|
1374
|
-
});
|
|
1375
|
-
|
|
1376
1265
|
test("non-interactive auto-compress continues without approval prompt", async () => {
|
|
1377
1266
|
const events: ServerMessage[] = [];
|
|
1378
1267
|
let callCount = 0;
|
|
@@ -1701,7 +1590,7 @@ describe("session-agent-loop", () => {
|
|
|
1701
1590
|
providerDurationMs: 100,
|
|
1702
1591
|
});
|
|
1703
1592
|
if (onCheckpoint) {
|
|
1704
|
-
const decision = onCheckpoint({
|
|
1593
|
+
const decision = await onCheckpoint({
|
|
1705
1594
|
turnIndex: 0,
|
|
1706
1595
|
toolCount: 1,
|
|
1707
1596
|
hasToolUse: true,
|
|
@@ -1769,7 +1658,7 @@ describe("session-agent-loop", () => {
|
|
|
1769
1658
|
providerDurationMs: 100,
|
|
1770
1659
|
});
|
|
1771
1660
|
if (onCheckpoint) {
|
|
1772
|
-
onCheckpoint({
|
|
1661
|
+
await onCheckpoint({
|
|
1773
1662
|
turnIndex: 0,
|
|
1774
1663
|
toolCount: 1,
|
|
1775
1664
|
hasToolUse: true,
|
|
@@ -2441,14 +2330,14 @@ describe("session-agent-loop", () => {
|
|
|
2441
2330
|
// The bulk-clear helper must have been called with the conversation id
|
|
2442
2331
|
// at least once (one of the three strip sites fired).
|
|
2443
2332
|
const clearCalls =
|
|
2444
|
-
|
|
2333
|
+
clearStrippedInjectionMetadataForConversationMock.mock.calls.filter(
|
|
2445
2334
|
(call) => call[0] === "test-conv",
|
|
2446
2335
|
);
|
|
2447
2336
|
expect(clearCalls.length).toBeGreaterThanOrEqual(1);
|
|
2448
2337
|
});
|
|
2449
2338
|
|
|
2450
2339
|
test("strip-site clear is non-fatal when the helper throws", async () => {
|
|
2451
|
-
|
|
2340
|
+
clearStrippedInjectionMetadataForConversationMock.mockImplementation(
|
|
2452
2341
|
() => {
|
|
2453
2342
|
throw new Error("db write failed");
|
|
2454
2343
|
},
|
|
@@ -195,6 +195,9 @@ mock.module("../agent/loop.js", () => ({
|
|
|
195
195
|
getToolTokenBudget() {
|
|
196
196
|
return 0;
|
|
197
197
|
}
|
|
198
|
+
getResolvedTools() {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
198
201
|
getActiveModel() {
|
|
199
202
|
return undefined;
|
|
200
203
|
}
|
|
@@ -203,7 +206,9 @@ mock.module("../agent/loop.js", () => ({
|
|
|
203
206
|
_onEvent: (event: AgentEvent) => void,
|
|
204
207
|
_signal?: AbortSignal,
|
|
205
208
|
_requestId?: string,
|
|
206
|
-
_onCheckpoint?: (
|
|
209
|
+
_onCheckpoint?: (
|
|
210
|
+
checkpoint: CheckpointInfo,
|
|
211
|
+
) => CheckpointDecision | Promise<CheckpointDecision>,
|
|
207
212
|
): Promise<Message[]> {
|
|
208
213
|
return [];
|
|
209
214
|
}
|
|
@@ -202,7 +202,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
202
202
|
getTurnTimeBounds: () => null,
|
|
203
203
|
PRIVATE_CONVERSATION_FORK_ERROR: "Cannot fork a private conversation",
|
|
204
204
|
countConversationsByScheduleJobId: () => 0,
|
|
205
|
-
|
|
205
|
+
selectSlackMetaCandidateMetadata: () => [],
|
|
206
206
|
forkConversation: () => null,
|
|
207
207
|
wipeConversation: () => ({
|
|
208
208
|
conversations: 0,
|
|
@@ -219,7 +219,7 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
219
219
|
getLastAssistantTimestampBefore: () => null,
|
|
220
220
|
archiveConversation: () => false,
|
|
221
221
|
unarchiveConversation: () => false,
|
|
222
|
-
|
|
222
|
+
clearStrippedInjectionMetadataForConversation: () => {},
|
|
223
223
|
}));
|
|
224
224
|
|
|
225
225
|
mock.module("../memory/conversation-queries.js", () => ({
|
|
@@ -233,13 +233,6 @@ mock.module("../memory/conversation-queries.js", () => ({
|
|
|
233
233
|
buildExcerpt: () => "",
|
|
234
234
|
}));
|
|
235
235
|
|
|
236
|
-
mock.module("../hooks/manager.js", () => ({
|
|
237
|
-
getHookManager: () => ({
|
|
238
|
-
trigger: () => Promise.resolve(),
|
|
239
|
-
initialize: () => {},
|
|
240
|
-
}),
|
|
241
|
-
}));
|
|
242
|
-
|
|
243
236
|
mock.module("../tools/watch/watch-state.js", () => ({
|
|
244
237
|
watchSessions: new Map(),
|
|
245
238
|
registerWatchStartNotifier: () => {},
|
|
@@ -292,13 +285,6 @@ mock.module("../calls/call-store.js", () => ({
|
|
|
292
285
|
finalizeCallbackClaim: () => true,
|
|
293
286
|
}));
|
|
294
287
|
|
|
295
|
-
mock.module("../daemon/watch-handler.js", () => ({
|
|
296
|
-
lastCommentaryByConversation: new Map(),
|
|
297
|
-
lastSummaryByConversation: new Map(),
|
|
298
|
-
handleWatchObservation: () => Promise.resolve(),
|
|
299
|
-
generateSummary: () => Promise.resolve(),
|
|
300
|
-
}));
|
|
301
|
-
|
|
302
288
|
mock.module("../tools/browser/browser-screencast.js", () => ({
|
|
303
289
|
registerConversationSender: () => {},
|
|
304
290
|
unregisterConversationSender: () => {},
|
|
@@ -358,8 +358,11 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
358
358
|
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
359
359
|
});
|
|
360
360
|
|
|
361
|
-
test("reuses pre-namespace binding when namespaced binding is absent", async () => {
|
|
362
|
-
// Simulate a binding created before the notification: prefix was introduced
|
|
361
|
+
test("reuses pre-namespace binding via inbound path when namespaced binding is absent", async () => {
|
|
362
|
+
// Simulate a binding created before the notification: prefix was introduced.
|
|
363
|
+
// Un-prefixed bindings are resolved by step 1 (inbound path) which skips
|
|
364
|
+
// the source check and does not upsert a notification-prefixed binding —
|
|
365
|
+
// the conversation is still reused.
|
|
363
366
|
mockExistingConversations["conv-legacy"] = {
|
|
364
367
|
id: "conv-legacy",
|
|
365
368
|
source: "notification",
|
|
@@ -390,16 +393,11 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
390
393
|
expect(result.conversationId).toBe("conv-legacy");
|
|
391
394
|
expect(result.createdNewConversation).toBe(false);
|
|
392
395
|
expect(createConversationMock).not.toHaveBeenCalled();
|
|
393
|
-
//
|
|
394
|
-
expect(upsertOutboundBindingMock).
|
|
395
|
-
const upsertArgs = upsertOutboundBindingMock.mock.calls[0]![0] as Record<
|
|
396
|
-
string,
|
|
397
|
-
unknown
|
|
398
|
-
>;
|
|
399
|
-
expect(upsertArgs.sourceChannel).toBe("notification:telegram");
|
|
396
|
+
// Inbound path does not touch outbound bindings — it only reads.
|
|
397
|
+
expect(upsertOutboundBindingMock).not.toHaveBeenCalled();
|
|
400
398
|
});
|
|
401
399
|
|
|
402
|
-
test("falls back to new conversation when bound conversation is stale (wrong source)", async () => {
|
|
400
|
+
test("falls back to new conversation when notification-bound conversation is stale (wrong source) and no inbound binding exists", async () => {
|
|
403
401
|
mockExistingConversations["conv-user-owned"] = {
|
|
404
402
|
id: "conv-user-owned",
|
|
405
403
|
source: "user",
|
|
@@ -410,6 +408,8 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
410
408
|
sourceChannel: "notification:slack",
|
|
411
409
|
externalChatId: "C0123ABCDEF",
|
|
412
410
|
};
|
|
411
|
+
// No inbound (un-prefixed) binding — step 1 finds nothing, step 2
|
|
412
|
+
// finds the notification binding but the source check rejects it.
|
|
413
413
|
|
|
414
414
|
const signal = makeSignal();
|
|
415
415
|
const copy = makeCopy();
|
|
@@ -468,6 +468,170 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
468
468
|
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
469
469
|
});
|
|
470
470
|
|
|
471
|
+
// ── Inbound conversation continuity ──────────────────────────────
|
|
472
|
+
|
|
473
|
+
test("prefers inbound conversation over notification-scoped conversation for reply continuity", async () => {
|
|
474
|
+
// An inbound conversation exists (un-prefixed binding, source: null)
|
|
475
|
+
// AND a notification conversation exists (prefixed binding, source: "notification").
|
|
476
|
+
// The inbound conversation should win so that the user's replies
|
|
477
|
+
// include the notification in their conversation history.
|
|
478
|
+
mockExistingConversations["conv-inbound"] = {
|
|
479
|
+
id: "conv-inbound",
|
|
480
|
+
source: null as unknown as string,
|
|
481
|
+
title: "Slack DM",
|
|
482
|
+
};
|
|
483
|
+
mockBindings["slack:D0ASABGUTQR"] = {
|
|
484
|
+
conversationId: "conv-inbound",
|
|
485
|
+
sourceChannel: "slack",
|
|
486
|
+
externalChatId: "D0ASABGUTQR",
|
|
487
|
+
};
|
|
488
|
+
mockExistingConversations["conv-notification"] = {
|
|
489
|
+
id: "conv-notification",
|
|
490
|
+
source: "notification",
|
|
491
|
+
title: "Notification Thread",
|
|
492
|
+
};
|
|
493
|
+
mockBindings["notification:slack:D0ASABGUTQR"] = {
|
|
494
|
+
conversationId: "conv-notification",
|
|
495
|
+
sourceChannel: "notification:slack",
|
|
496
|
+
externalChatId: "D0ASABGUTQR",
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const signal = makeSignal();
|
|
500
|
+
const copy = makeCopy({
|
|
501
|
+
conversationSeedMessage: "New tweet from @alice - draft reply attached",
|
|
502
|
+
});
|
|
503
|
+
const bindingContext: DestinationBindingContext = {
|
|
504
|
+
sourceChannel: "slack" as NotificationChannel,
|
|
505
|
+
externalChatId: "D0ASABGUTQR",
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const result = await pairDeliveryWithConversation(
|
|
509
|
+
signal,
|
|
510
|
+
"slack" as NotificationChannel,
|
|
511
|
+
copy,
|
|
512
|
+
{ bindingContext },
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
// Should use the inbound conversation, not the notification one
|
|
516
|
+
expect(result.conversationId).toBe("conv-inbound");
|
|
517
|
+
expect(result.messageId).toBe("msg-001");
|
|
518
|
+
expect(result.createdNewConversation).toBe(false);
|
|
519
|
+
expect(result.conversationFallbackUsed).toBe(false);
|
|
520
|
+
expect(createConversationMock).not.toHaveBeenCalled();
|
|
521
|
+
expect(addMessageMock).toHaveBeenCalledTimes(1);
|
|
522
|
+
expect(addMessageMock.mock.calls[0]![0]).toBe("conv-inbound");
|
|
523
|
+
// Should NOT touch the notification binding — we only read the inbound one
|
|
524
|
+
expect(upsertOutboundBindingMock).not.toHaveBeenCalled();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("uses inbound conversation regardless of source field for reply continuity", async () => {
|
|
528
|
+
// The inbound conversation has source: null (typical for conversations
|
|
529
|
+
// created by the inbound handler). The notification would normally
|
|
530
|
+
// skip this because effectiveSource is "notification". But the inbound
|
|
531
|
+
// path intentionally skips the source check.
|
|
532
|
+
mockExistingConversations["conv-inbound-null-source"] = {
|
|
533
|
+
id: "conv-inbound-null-source",
|
|
534
|
+
source: null as unknown as string,
|
|
535
|
+
title: "Slack DM",
|
|
536
|
+
};
|
|
537
|
+
mockBindings["slack:D0CHATID123"] = {
|
|
538
|
+
conversationId: "conv-inbound-null-source",
|
|
539
|
+
sourceChannel: "slack",
|
|
540
|
+
externalChatId: "D0CHATID123",
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
const signal = makeSignal();
|
|
544
|
+
const copy = makeCopy({
|
|
545
|
+
conversationSeedMessage: "Your daily briefing is ready",
|
|
546
|
+
});
|
|
547
|
+
const bindingContext: DestinationBindingContext = {
|
|
548
|
+
sourceChannel: "slack" as NotificationChannel,
|
|
549
|
+
externalChatId: "D0CHATID123",
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const result = await pairDeliveryWithConversation(
|
|
553
|
+
signal,
|
|
554
|
+
"slack" as NotificationChannel,
|
|
555
|
+
copy,
|
|
556
|
+
{ bindingContext },
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
expect(result.conversationId).toBe("conv-inbound-null-source");
|
|
560
|
+
expect(result.createdNewConversation).toBe(false);
|
|
561
|
+
expect(createConversationMock).not.toHaveBeenCalled();
|
|
562
|
+
expect(addMessageMock.mock.calls[0]![0]).toBe("conv-inbound-null-source");
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test("falls back to notification binding when inbound binding points to deleted conversation", async () => {
|
|
566
|
+
// Inbound binding exists but conversation was deleted.
|
|
567
|
+
// Should fall through to notification binding.
|
|
568
|
+
mockBindings["slack:D0STALE"] = {
|
|
569
|
+
conversationId: "conv-deleted-inbound",
|
|
570
|
+
sourceChannel: "slack",
|
|
571
|
+
externalChatId: "D0STALE",
|
|
572
|
+
};
|
|
573
|
+
// conv-deleted-inbound is NOT in mockExistingConversations — getConversation returns null
|
|
574
|
+
|
|
575
|
+
mockExistingConversations["conv-notification-fallback"] = {
|
|
576
|
+
id: "conv-notification-fallback",
|
|
577
|
+
source: "notification",
|
|
578
|
+
title: "Notification Thread",
|
|
579
|
+
};
|
|
580
|
+
mockBindings["notification:slack:D0STALE"] = {
|
|
581
|
+
conversationId: "conv-notification-fallback",
|
|
582
|
+
sourceChannel: "notification:slack",
|
|
583
|
+
externalChatId: "D0STALE",
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const signal = makeSignal();
|
|
587
|
+
const copy = makeCopy();
|
|
588
|
+
const bindingContext: DestinationBindingContext = {
|
|
589
|
+
sourceChannel: "slack" as NotificationChannel,
|
|
590
|
+
externalChatId: "D0STALE",
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const result = await pairDeliveryWithConversation(
|
|
594
|
+
signal,
|
|
595
|
+
"slack" as NotificationChannel,
|
|
596
|
+
copy,
|
|
597
|
+
{ bindingContext },
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
// Inbound conversation is gone — should fall back to notification conversation
|
|
601
|
+
expect(result.conversationId).toBe("conv-notification-fallback");
|
|
602
|
+
expect(result.createdNewConversation).toBe(false);
|
|
603
|
+
expect(createConversationMock).not.toHaveBeenCalled();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test("falls through to create new conversation when no inbound and no notification binding exists", async () => {
|
|
607
|
+
// First notification to a channel where user has never messaged.
|
|
608
|
+
// No bindings at all — should create a new conversation.
|
|
609
|
+
const signal = makeSignal();
|
|
610
|
+
const copy = makeCopy();
|
|
611
|
+
const bindingContext: DestinationBindingContext = {
|
|
612
|
+
sourceChannel: "slack" as NotificationChannel,
|
|
613
|
+
externalChatId: "D0BRANDNEW",
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const result = await pairDeliveryWithConversation(
|
|
617
|
+
signal,
|
|
618
|
+
"slack" as NotificationChannel,
|
|
619
|
+
copy,
|
|
620
|
+
{ bindingContext },
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
expect(result.conversationId).toBe("conv-001");
|
|
624
|
+
expect(result.createdNewConversation).toBe(true);
|
|
625
|
+
expect(createConversationMock).toHaveBeenCalledTimes(1);
|
|
626
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
627
|
+
const upsertArgs = upsertOutboundBindingMock.mock.calls[0]![0] as Record<
|
|
628
|
+
string,
|
|
629
|
+
unknown
|
|
630
|
+
>;
|
|
631
|
+
expect(upsertArgs.sourceChannel).toBe("notification:slack");
|
|
632
|
+
expect(upsertArgs.externalChatId).toBe("D0BRANDNEW");
|
|
633
|
+
});
|
|
634
|
+
|
|
471
635
|
test("creates new conversation and upserts binding when no prior binding exists", async () => {
|
|
472
636
|
const signal = makeSignal();
|
|
473
637
|
const copy = makeCopy();
|
|
@@ -23,7 +23,7 @@ mock.module("../providers/registry.js", () => ({
|
|
|
23
23
|
mock.module("../config/loader.js", () => ({
|
|
24
24
|
getConfig: () => ({
|
|
25
25
|
ui: {},
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
llm: {
|
|
28
28
|
default: {
|
|
29
29
|
provider: "mock-provider",
|
|
@@ -141,6 +141,9 @@ mock.module("../agent/loop.js", () => ({
|
|
|
141
141
|
getToolTokenBudget() {
|
|
142
142
|
return 0;
|
|
143
143
|
}
|
|
144
|
+
getResolvedTools() {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
144
147
|
getActiveModel() {
|
|
145
148
|
return undefined;
|
|
146
149
|
}
|
|
@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { AgentEvent } from "../agent/loop.js";
|
|
4
4
|
import type { UserMessageAttachment } from "../daemon/message-protocol.js";
|
|
5
|
+
import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
|
|
5
6
|
import type { Message, ProviderResponse } from "../providers/types.js";
|
|
6
7
|
import { ProviderError } from "../util/errors.js";
|
|
7
8
|
|
|
@@ -26,7 +27,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
26
27
|
daemon: {
|
|
27
28
|
titleGenerationMaxTokens: 30,
|
|
28
29
|
},
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
llm: {
|
|
31
32
|
default: {
|
|
32
33
|
provider: "mock-provider",
|
|
@@ -76,9 +77,13 @@ mock.module("../config/loader.js", () => ({
|
|
|
76
77
|
}));
|
|
77
78
|
|
|
78
79
|
// Token estimator: return a small value (well within budget) so preflight
|
|
79
|
-
// does not trigger in existing tests.
|
|
80
|
+
// does not trigger in existing tests. Stub both the calibrated and raw
|
|
81
|
+
// entry points — the latter backs the default `tokenEstimate` plugin
|
|
82
|
+
// pipeline now used by the orchestrator's preflight / mid-loop checkpoints.
|
|
80
83
|
mock.module("../context/token-estimator.js", () => ({
|
|
81
84
|
estimatePromptTokens: () => 1000,
|
|
85
|
+
estimatePromptTokensRaw: () => 1000,
|
|
86
|
+
estimateToolsTokens: () => 0,
|
|
82
87
|
}));
|
|
83
88
|
|
|
84
89
|
// Overflow recovery module mocks — the convergence loop delegates to these
|
|
@@ -140,11 +145,6 @@ mock.module("../daemon/context-overflow-policy.js", () => ({
|
|
|
140
145
|
resolveOverflowAction: () => "fail_gracefully",
|
|
141
146
|
}));
|
|
142
147
|
|
|
143
|
-
mock.module("../daemon/context-overflow-approval.js", () => ({
|
|
144
|
-
requestCompressionApproval: async () => ({ approved: false }),
|
|
145
|
-
CONTEXT_OVERFLOW_TOOL_NAME: "context_overflow_compression",
|
|
146
|
-
}));
|
|
147
|
-
|
|
148
148
|
mock.module("../prompts/system-prompt.js", () => ({
|
|
149
149
|
buildSystemPrompt: () => "system prompt",
|
|
150
150
|
}));
|
|
@@ -261,6 +261,9 @@ mock.module("../agent/loop.js", () => ({
|
|
|
261
261
|
getToolTokenBudget() {
|
|
262
262
|
return 0;
|
|
263
263
|
}
|
|
264
|
+
getResolvedTools() {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
264
267
|
getActiveModel() {
|
|
265
268
|
return undefined;
|
|
266
269
|
}
|
|
@@ -445,6 +448,12 @@ describe("provider ordering error retry", () => {
|
|
|
445
448
|
firstRunErrorMode = "ordering";
|
|
446
449
|
maybeCompactCalls = [];
|
|
447
450
|
forceCompactionEnabled = false;
|
|
451
|
+
// Orchestrator pipelines (`overflowReduce`, `persistence`, …) run through
|
|
452
|
+
// the plugin registry; re-register every default so each pipeline has a
|
|
453
|
+
// middleware to dispatch to. The `context-overflow-reducer` module itself
|
|
454
|
+
// (and other collaborators) are mocked above, so the default plugins'
|
|
455
|
+
// delegates go through the mocked implementations.
|
|
456
|
+
resetPluginRegistryAndRegisterDefaults();
|
|
448
457
|
});
|
|
449
458
|
|
|
450
459
|
test("simulated strict provider error triggers exactly one retry", async () => {
|