@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,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `emptyResponse` plugin pipeline (PR 18).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Default plugin decision matches the original inline loop logic for the
|
|
6
|
+
* canonical cases (empty-after-tools → nudge, visible-text → accept,
|
|
7
|
+
* tool-use-blocks-present → accept, retries-exhausted → accept,
|
|
8
|
+
* prior-visible-text-in-run → accept).
|
|
9
|
+
* - Swapping in a custom middleware that returns `action: "accept"` prevents
|
|
10
|
+
* the nudge and lets the loop fall through to history append.
|
|
11
|
+
* - Swapping in a custom middleware that returns `action: "error"` is
|
|
12
|
+
* propagated by the pipeline so the loop can surface a clear error.
|
|
13
|
+
*
|
|
14
|
+
* The loop's actual side-effects (history append, retry counter bump, log
|
|
15
|
+
* emission) live in `agent/loop.ts` and are covered by integration tests in
|
|
16
|
+
* `conversation-agent-loop.test.ts`. This file isolates the pipeline.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
20
|
+
|
|
21
|
+
import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
|
|
22
|
+
import {
|
|
23
|
+
defaultEmptyResponsePlugin,
|
|
24
|
+
defaultEmptyResponseTerminal,
|
|
25
|
+
} from "../plugins/defaults/empty-response.js";
|
|
26
|
+
import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
|
|
27
|
+
import {
|
|
28
|
+
getMiddlewaresFor,
|
|
29
|
+
registerPlugin,
|
|
30
|
+
resetPluginRegistryForTests,
|
|
31
|
+
} from "../plugins/registry.js";
|
|
32
|
+
import type {
|
|
33
|
+
EmptyResponseArgs,
|
|
34
|
+
EmptyResponseDecision,
|
|
35
|
+
Middleware,
|
|
36
|
+
Plugin,
|
|
37
|
+
TurnContext,
|
|
38
|
+
} from "../plugins/types.js";
|
|
39
|
+
import type { ContentBlock } from "../providers/types.js";
|
|
40
|
+
|
|
41
|
+
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const trust: TrustContext = {
|
|
44
|
+
sourceChannel: "vellum",
|
|
45
|
+
trustClass: "guardian",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function makeCtx(): TurnContext {
|
|
49
|
+
return {
|
|
50
|
+
requestId: "req-empty-response",
|
|
51
|
+
conversationId: "conv-empty-response",
|
|
52
|
+
turnIndex: 2,
|
|
53
|
+
trust,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The nudge text has to match the loop's original inline string verbatim —
|
|
59
|
+
* clients (and the model) may match on this exact text.
|
|
60
|
+
*/
|
|
61
|
+
const CANONICAL_NUDGE_TEXT =
|
|
62
|
+
"<system_notice>Your previous response was empty. You must respond to the user with a summary of what you found or did. Do not use any tools — just respond with text.</system_notice>";
|
|
63
|
+
|
|
64
|
+
const emptyTextBlock: ContentBlock = { type: "text", text: " " };
|
|
65
|
+
|
|
66
|
+
function makeArgs(
|
|
67
|
+
overrides: Partial<EmptyResponseArgs> = {},
|
|
68
|
+
): EmptyResponseArgs {
|
|
69
|
+
return {
|
|
70
|
+
responseContent: [],
|
|
71
|
+
toolUseBlocksLength: 0,
|
|
72
|
+
toolUseTurns: 1,
|
|
73
|
+
emptyResponseRetries: 0,
|
|
74
|
+
maxEmptyResponseRetries: 1,
|
|
75
|
+
priorAssistantHadVisibleText: false,
|
|
76
|
+
...overrides,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function runEmpty(
|
|
81
|
+
args: EmptyResponseArgs,
|
|
82
|
+
): Promise<EmptyResponseDecision> {
|
|
83
|
+
return runPipeline(
|
|
84
|
+
"emptyResponse",
|
|
85
|
+
getMiddlewaresFor("emptyResponse"),
|
|
86
|
+
async (a) => defaultEmptyResponseTerminal(a),
|
|
87
|
+
args,
|
|
88
|
+
makeCtx(),
|
|
89
|
+
DEFAULT_TIMEOUTS.emptyResponse,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
describe("emptyResponse pipeline — default decisions", () => {
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
resetPluginRegistryForTests();
|
|
98
|
+
registerPlugin(defaultEmptyResponsePlugin);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("empty turn after tool results → nudge with canonical text", async () => {
|
|
102
|
+
// Whitespace-only text counts as empty (matches inline `trim().length > 0`).
|
|
103
|
+
const decision = await runEmpty(
|
|
104
|
+
makeArgs({
|
|
105
|
+
responseContent: [emptyTextBlock],
|
|
106
|
+
toolUseBlocksLength: 0,
|
|
107
|
+
toolUseTurns: 2,
|
|
108
|
+
emptyResponseRetries: 0,
|
|
109
|
+
priorAssistantHadVisibleText: false,
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
expect(decision.action).toBe("nudge");
|
|
113
|
+
expect(decision.nudgeText).toBe(CANONICAL_NUDGE_TEXT);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("turn contains visible text → accept", async () => {
|
|
117
|
+
const decision = await runEmpty(
|
|
118
|
+
makeArgs({
|
|
119
|
+
responseContent: [{ type: "text", text: "here is a summary" }],
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
expect(decision.action).toBe("accept");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("turn contains tool_use blocks → accept (not empty)", async () => {
|
|
126
|
+
const decision = await runEmpty(
|
|
127
|
+
makeArgs({
|
|
128
|
+
responseContent: [
|
|
129
|
+
{
|
|
130
|
+
type: "tool_use",
|
|
131
|
+
id: "tu-1",
|
|
132
|
+
name: "read",
|
|
133
|
+
input: { path: "/tmp/x" },
|
|
134
|
+
} as ContentBlock,
|
|
135
|
+
],
|
|
136
|
+
toolUseBlocksLength: 1,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
expect(decision.action).toBe("accept");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("retries already exhausted → accept", async () => {
|
|
143
|
+
const decision = await runEmpty(
|
|
144
|
+
makeArgs({
|
|
145
|
+
responseContent: [],
|
|
146
|
+
toolUseTurns: 3,
|
|
147
|
+
emptyResponseRetries: 1,
|
|
148
|
+
maxEmptyResponseRetries: 1,
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
expect(decision.action).toBe("accept");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("prior assistant turn already delivered visible text → accept", async () => {
|
|
155
|
+
// Model said its piece earlier, ended with a side-effect tool, returned
|
|
156
|
+
// empty. Nudging would force a verbatim re-send of text the user already
|
|
157
|
+
// saw. Default must accept.
|
|
158
|
+
const decision = await runEmpty(
|
|
159
|
+
makeArgs({
|
|
160
|
+
responseContent: [],
|
|
161
|
+
toolUseTurns: 2,
|
|
162
|
+
priorAssistantHadVisibleText: true,
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
expect(decision.action).toBe("accept");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("no prior tool-use turn (toolUseTurns === 0) → accept", async () => {
|
|
169
|
+
// Empty first assistant response with no tools is not the pattern the
|
|
170
|
+
// nudge guards against. Default accepts.
|
|
171
|
+
const decision = await runEmpty(
|
|
172
|
+
makeArgs({
|
|
173
|
+
responseContent: [],
|
|
174
|
+
toolUseTurns: 0,
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
expect(decision.action).toBe("accept");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("emptyResponse pipeline — custom middleware overrides", () => {
|
|
182
|
+
beforeEach(() => {
|
|
183
|
+
resetPluginRegistryForTests();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("plugin returning action:accept suppresses the nudge", async () => {
|
|
187
|
+
// Build a plugin whose middleware short-circuits with accept. Register it
|
|
188
|
+
// as the ONLY plugin so its decision is authoritative. The loop-side
|
|
189
|
+
// effect (no nudge appended) is covered by integration tests; here we
|
|
190
|
+
// assert the pipeline returns what the plugin returned.
|
|
191
|
+
const acceptPlugin: Plugin = {
|
|
192
|
+
manifest: {
|
|
193
|
+
name: "force-accept",
|
|
194
|
+
version: "1.0.0",
|
|
195
|
+
requires: { pluginRuntime: "v1" },
|
|
196
|
+
},
|
|
197
|
+
middleware: {
|
|
198
|
+
emptyResponse: async () => ({ action: "accept" }),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
registerPlugin(acceptPlugin);
|
|
202
|
+
|
|
203
|
+
const decision = await runEmpty(
|
|
204
|
+
makeArgs({
|
|
205
|
+
// Conditions the default would nudge on — but the custom plugin wins.
|
|
206
|
+
responseContent: [],
|
|
207
|
+
toolUseTurns: 2,
|
|
208
|
+
emptyResponseRetries: 0,
|
|
209
|
+
priorAssistantHadVisibleText: false,
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
expect(decision.action).toBe("accept");
|
|
213
|
+
// `nudgeText` must not leak from the acceptance branch.
|
|
214
|
+
expect(decision.nudgeText).toBeUndefined();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("plugin returning action:error is propagated to the caller", async () => {
|
|
218
|
+
const errorPlugin: Plugin = {
|
|
219
|
+
manifest: {
|
|
220
|
+
name: "force-error",
|
|
221
|
+
version: "1.0.0",
|
|
222
|
+
requires: { pluginRuntime: "v1" },
|
|
223
|
+
},
|
|
224
|
+
middleware: {
|
|
225
|
+
emptyResponse: async () => ({ action: "error" }),
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
registerPlugin(errorPlugin);
|
|
229
|
+
|
|
230
|
+
const decision = await runEmpty(makeArgs());
|
|
231
|
+
expect(decision.action).toBe("error");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("plugin overriding default nudge text changes the returned text", async () => {
|
|
235
|
+
// Exercises the wrapping semantics: the custom plugin observes the
|
|
236
|
+
// default's decision via `next(args)` and rewrites only the text. This
|
|
237
|
+
// is the canonical "plugin wraps default" pattern.
|
|
238
|
+
const rewriterPlugin: Plugin = {
|
|
239
|
+
manifest: {
|
|
240
|
+
name: "rewrite-nudge",
|
|
241
|
+
version: "1.0.0",
|
|
242
|
+
requires: { pluginRuntime: "v1" },
|
|
243
|
+
},
|
|
244
|
+
middleware: {
|
|
245
|
+
emptyResponse: async (args, next, ctx) => {
|
|
246
|
+
const downstream = await next(args);
|
|
247
|
+
if (downstream.action !== "nudge") return downstream;
|
|
248
|
+
void ctx; // silence lint
|
|
249
|
+
return { action: "nudge", nudgeText: "ALTERED_NUDGE" };
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
// Register the custom plugin FIRST so it is the outermost middleware; the
|
|
254
|
+
// default registers second and acts as the inner decision maker.
|
|
255
|
+
registerPlugin(rewriterPlugin);
|
|
256
|
+
registerPlugin(defaultEmptyResponsePlugin);
|
|
257
|
+
|
|
258
|
+
const decision = await runEmpty(
|
|
259
|
+
makeArgs({
|
|
260
|
+
responseContent: [],
|
|
261
|
+
toolUseTurns: 2,
|
|
262
|
+
priorAssistantHadVisibleText: false,
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
expect(decision.action).toBe("nudge");
|
|
266
|
+
expect(decision.nudgeText).toBe("ALTERED_NUDGE");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("user plugin registered AFTER the default still runs (no shadowing)", async () => {
|
|
270
|
+
// Production registration order: defaults load first via the side-effect
|
|
271
|
+
// imports in `defaults/index.ts`, then user plugins register on top via
|
|
272
|
+
// `bootstrapPlugins()`. The user's middleware ends up at a deeper onion
|
|
273
|
+
// layer than the default. If the default's middleware were to bypass
|
|
274
|
+
// `next` and decide directly, the user middleware would never run — this
|
|
275
|
+
// test guards against that regression.
|
|
276
|
+
registerPlugin(defaultEmptyResponsePlugin);
|
|
277
|
+
|
|
278
|
+
let userMiddlewareRan = false;
|
|
279
|
+
const userMiddleware: Middleware<
|
|
280
|
+
EmptyResponseArgs,
|
|
281
|
+
EmptyResponseDecision
|
|
282
|
+
> = async (args, next) => {
|
|
283
|
+
userMiddlewareRan = true;
|
|
284
|
+
return next(args);
|
|
285
|
+
};
|
|
286
|
+
registerPlugin({
|
|
287
|
+
manifest: {
|
|
288
|
+
name: "late-user-empty-response",
|
|
289
|
+
version: "0.0.1",
|
|
290
|
+
requires: { pluginRuntime: "v1", emptyResponseApi: "v1" },
|
|
291
|
+
},
|
|
292
|
+
middleware: { emptyResponse: userMiddleware },
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
await runEmpty(
|
|
296
|
+
makeArgs({
|
|
297
|
+
responseContent: [],
|
|
298
|
+
toolUseTurns: 2,
|
|
299
|
+
priorAssistantHadVisibleText: false,
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
expect(userMiddlewareRan).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
@@ -16,7 +16,7 @@ import { homedir } from "node:os";
|
|
|
16
16
|
import { join, resolve } from "node:path";
|
|
17
17
|
import { describe, expect, test } from "bun:test";
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { getAllowedExtensionOrigins } from "../runtime/routes/browser-extension-pair-routes.js";
|
|
20
20
|
|
|
21
21
|
const repoRoot = resolve(__dirname, "..", "..", "..");
|
|
22
22
|
const CANONICAL_CONFIG_REL_PATH =
|
|
@@ -176,7 +176,7 @@ describe("Chrome extension allowlist guard", () => {
|
|
|
176
176
|
const config = parseCanonicalConfig();
|
|
177
177
|
for (const id of config.allowedExtensionIds) {
|
|
178
178
|
const origin = `chrome-extension://${id}/`;
|
|
179
|
-
expect(
|
|
179
|
+
expect(getAllowedExtensionOrigins().has(origin)).toBe(true);
|
|
180
180
|
}
|
|
181
181
|
});
|
|
182
182
|
|
|
@@ -198,7 +198,7 @@ describe("Chrome extension allowlist guard", () => {
|
|
|
198
198
|
const expectedOrigins = new Set(
|
|
199
199
|
config.allowedExtensionIds.map((id) => `chrome-extension://${id}/`),
|
|
200
200
|
);
|
|
201
|
-
expect(new Set(
|
|
201
|
+
expect(new Set(getAllowedExtensionOrigins())).toEqual(expectedOrigins);
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
test("concrete extension IDs appear only in canonical config or CWS URLs", () => {
|
|
@@ -6,6 +6,7 @@ const tempDir = process.env.VELLUM_WORKSPACE_DIR!;
|
|
|
6
6
|
|
|
7
7
|
const { isWakeUpGreeting, getCannedFirstGreeting, CANNED_FIRST_GREETING } =
|
|
8
8
|
await import("../daemon/first-greeting.js");
|
|
9
|
+
import type { OnboardingGreetingContext } from "../daemon/first-greeting.js";
|
|
9
10
|
|
|
10
11
|
describe("first-greeting", () => {
|
|
11
12
|
describe("isWakeUpGreeting", () => {
|
|
@@ -48,12 +49,253 @@ describe("first-greeting", () => {
|
|
|
48
49
|
});
|
|
49
50
|
});
|
|
50
51
|
|
|
51
|
-
describe("
|
|
52
|
-
it("returns
|
|
53
|
-
|
|
52
|
+
describe("no-onboarding branch", () => {
|
|
53
|
+
it("returns no-onboarding greeting when context is undefined", () => {
|
|
54
|
+
expect(getCannedFirstGreeting(undefined)).toBe(CANNED_FIRST_GREETING);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns no-onboarding greeting when everything is empty", () => {
|
|
58
|
+
const greeting = getCannedFirstGreeting({
|
|
59
|
+
tools: [],
|
|
60
|
+
tasks: [],
|
|
61
|
+
tone: "",
|
|
62
|
+
});
|
|
54
63
|
expect(greeting).toBe(CANNED_FIRST_GREETING);
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("no-onboarding greeting uses two-paragraph structure", () => {
|
|
67
|
+
expect(CANNED_FIRST_GREETING).toContain("\n\n");
|
|
68
|
+
expect(CANNED_FIRST_GREETING).toContain("I can ask");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("personalized greeting", () => {
|
|
73
|
+
const base: OnboardingGreetingContext = {
|
|
74
|
+
tools: [],
|
|
75
|
+
tasks: [],
|
|
76
|
+
tone: "balanced",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
it("full dev stack: GitHub + Linear, Building + PM", () => {
|
|
80
|
+
const greeting = getCannedFirstGreeting({
|
|
81
|
+
...base,
|
|
82
|
+
tools: ["github", "linear"],
|
|
83
|
+
tasks: ["code-building", "project-management"],
|
|
84
|
+
userName: "Bob",
|
|
85
|
+
assistantName: "Pip",
|
|
86
|
+
});
|
|
87
|
+
expect(greeting).toContain("Hey Bob, I'm Pip.");
|
|
88
|
+
expect(greeting).toContain("You mentioned using GitHub and Linear");
|
|
89
|
+
expect(greeting).toContain(
|
|
90
|
+
"shipping code or figuring out what to ship next",
|
|
91
|
+
);
|
|
92
|
+
expect(greeting).toContain("\n\n");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("PM + comms: Linear + Notion, PM + Writing", () => {
|
|
96
|
+
const greeting = getCannedFirstGreeting({
|
|
97
|
+
...base,
|
|
98
|
+
tools: ["linear", "notion"],
|
|
99
|
+
tasks: ["project-management", "writing"],
|
|
100
|
+
userName: "Bob",
|
|
101
|
+
assistantName: "Pip",
|
|
102
|
+
});
|
|
103
|
+
expect(greeting).toContain("You mentioned using Notion and Linear");
|
|
104
|
+
expect(greeting).toContain("writing a spec or pushing something forward");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("writer: Notion + Google Drive, Writing only", () => {
|
|
108
|
+
const greeting = getCannedFirstGreeting({
|
|
109
|
+
...base,
|
|
110
|
+
tools: ["notion", "google-drive"],
|
|
111
|
+
tasks: ["writing"],
|
|
112
|
+
userName: "Bob",
|
|
113
|
+
assistantName: "Luna",
|
|
114
|
+
});
|
|
115
|
+
expect(greeting).toContain("You mentioned using Notion and Google Drive");
|
|
116
|
+
expect(greeting).toContain("drafting something or cleaning up docs");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("single task no tools: Building only", () => {
|
|
120
|
+
const greeting = getCannedFirstGreeting({
|
|
121
|
+
...base,
|
|
122
|
+
tasks: ["code-building"],
|
|
123
|
+
userName: "Bob",
|
|
124
|
+
assistantName: "Pip",
|
|
125
|
+
});
|
|
126
|
+
expect(greeting).toContain("Probably shipping something or debugging");
|
|
127
|
+
expect(greeting).not.toContain("You mentioned using");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("single tool single task: GitHub + Building", () => {
|
|
131
|
+
const greeting = getCannedFirstGreeting({
|
|
132
|
+
...base,
|
|
133
|
+
tools: ["github"],
|
|
134
|
+
tasks: ["code-building"],
|
|
135
|
+
userName: "Bob",
|
|
136
|
+
assistantName: "Pip",
|
|
137
|
+
});
|
|
138
|
+
expect(greeting).toContain("You mentioned using GitHub");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("many hats: 4 selections", () => {
|
|
142
|
+
const greeting = getCannedFirstGreeting({
|
|
143
|
+
...base,
|
|
144
|
+
tasks: ["code-building", "writing", "research", "project-management"],
|
|
145
|
+
userName: "Bob",
|
|
146
|
+
assistantName: "Pip",
|
|
147
|
+
});
|
|
148
|
+
expect(greeting).toContain("wear a lot of hats");
|
|
149
|
+
expect(greeting).toContain("Where should we start?");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("many hats: 6 selections", () => {
|
|
153
|
+
const greeting = getCannedFirstGreeting({
|
|
154
|
+
...base,
|
|
155
|
+
tasks: [
|
|
156
|
+
"code-building",
|
|
157
|
+
"writing",
|
|
158
|
+
"research",
|
|
159
|
+
"project-management",
|
|
160
|
+
"scheduling",
|
|
161
|
+
"personal",
|
|
162
|
+
],
|
|
163
|
+
userName: "Bob",
|
|
164
|
+
assistantName: "Pip",
|
|
165
|
+
});
|
|
166
|
+
expect(greeting).toContain("wear a lot of hats");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("no tasks with tools: no-signal branch", () => {
|
|
170
|
+
const greeting = getCannedFirstGreeting({
|
|
171
|
+
...base,
|
|
172
|
+
tools: ["gmail", "linear"],
|
|
173
|
+
tasks: [],
|
|
174
|
+
userName: "Bob",
|
|
175
|
+
assistantName: "Pip",
|
|
176
|
+
});
|
|
177
|
+
expect(greeting).toContain("What's on your plate?");
|
|
178
|
+
expect(greeting).toContain("I can ask you a few questions");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("missing name uses Hey comma opener", () => {
|
|
182
|
+
const greeting = getCannedFirstGreeting({
|
|
183
|
+
...base,
|
|
184
|
+
tasks: ["code-building"],
|
|
185
|
+
assistantName: "Pip",
|
|
186
|
+
});
|
|
187
|
+
expect(greeting).toStartWith("Hey, I'm Pip.");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("3-selection falls back to highest-priority single template", () => {
|
|
191
|
+
const greeting = getCannedFirstGreeting({
|
|
192
|
+
...base,
|
|
193
|
+
tasks: ["writing", "research", "scheduling"],
|
|
194
|
+
userName: "Bob",
|
|
195
|
+
assistantName: "Pip",
|
|
196
|
+
});
|
|
197
|
+
expect(greeting).toContain("drafting something or cleaning up docs");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("unlisted 2-combo falls back to highest-priority single", () => {
|
|
201
|
+
const greeting = getCannedFirstGreeting({
|
|
202
|
+
...base,
|
|
203
|
+
tasks: ["research", "personal"],
|
|
204
|
+
userName: "Bob",
|
|
205
|
+
assistantName: "Pip",
|
|
206
|
+
});
|
|
207
|
+
expect(greeting).toContain(
|
|
208
|
+
"digging into a topic or making sense of something",
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("uses capital I throughout", () => {
|
|
213
|
+
const greeting = getCannedFirstGreeting({
|
|
214
|
+
...base,
|
|
215
|
+
tasks: ["code-building"],
|
|
216
|
+
userName: "Bob",
|
|
217
|
+
assistantName: "Pip",
|
|
218
|
+
});
|
|
219
|
+
expect(greeting).toContain("I'm Pip");
|
|
220
|
+
expect(greeting).toContain("I'll get sharper");
|
|
221
|
+
expect(greeting).toContain("Am I on the right track");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("two-paragraph structure with blank line", () => {
|
|
225
|
+
const greeting = getCannedFirstGreeting({
|
|
226
|
+
...base,
|
|
227
|
+
tasks: ["code-building"],
|
|
228
|
+
userName: "Bob",
|
|
229
|
+
assistantName: "Pip",
|
|
230
|
+
});
|
|
231
|
+
const paragraphs = greeting.split("\n\n");
|
|
232
|
+
expect(paragraphs.length).toBe(2);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("picks relevant tools for guess, not arbitrary selection order", () => {
|
|
236
|
+
const greeting = getCannedFirstGreeting({
|
|
237
|
+
...base,
|
|
238
|
+
tools: [
|
|
239
|
+
"notion",
|
|
240
|
+
"linear",
|
|
241
|
+
"gmail",
|
|
242
|
+
"google-calendar",
|
|
243
|
+
"github",
|
|
244
|
+
"apple-notes",
|
|
245
|
+
],
|
|
246
|
+
tasks: ["code-building", "project-management"],
|
|
247
|
+
userName: "Bob",
|
|
248
|
+
assistantName: "Pip",
|
|
249
|
+
});
|
|
250
|
+
expect(greeting).toContain("You mentioned using GitHub and Linear");
|
|
251
|
+
expect(greeting).not.toContain("Apple Notes");
|
|
252
|
+
expect(greeting).not.toContain("Notion");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("falls back to no-tool prefix when no relevant tools match", () => {
|
|
256
|
+
const greeting = getCannedFirstGreeting({
|
|
257
|
+
...base,
|
|
258
|
+
tools: ["notion", "apple-notes"],
|
|
259
|
+
tasks: ["code-building"],
|
|
260
|
+
userName: "Bob",
|
|
261
|
+
assistantName: "Pip",
|
|
262
|
+
});
|
|
263
|
+
expect(greeting).toContain("Probably shipping something or debugging");
|
|
264
|
+
expect(greeting).not.toContain("You mentioned using");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("life admin guess uses verb phrase", () => {
|
|
268
|
+
const greeting = getCannedFirstGreeting({
|
|
269
|
+
...base,
|
|
270
|
+
tools: ["gmail", "google-calendar"],
|
|
271
|
+
tasks: ["personal"],
|
|
272
|
+
userName: "Bob",
|
|
273
|
+
assistantName: "Pip",
|
|
274
|
+
});
|
|
275
|
+
expect(greeting).toContain(
|
|
276
|
+
"You mentioned using Gmail and Google Calendar",
|
|
277
|
+
);
|
|
278
|
+
expect(greeting).toContain("juggling travel, bills, or household stuff");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("uses personalized greeting when assistantName present but no user name/tasks/tools", () => {
|
|
282
|
+
const greeting = getCannedFirstGreeting({
|
|
283
|
+
...base,
|
|
284
|
+
assistantName: "Pip",
|
|
285
|
+
});
|
|
286
|
+
expect(greeting).toContain("I'm Pip");
|
|
287
|
+
expect(greeting).not.toContain("no name, no memories");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("falls back to no-signal branch for unknown task types", () => {
|
|
291
|
+
const greeting = getCannedFirstGreeting({
|
|
292
|
+
...base,
|
|
293
|
+
tasks: ["future-task-type"],
|
|
294
|
+
userName: "Bob",
|
|
295
|
+
assistantName: "Pip",
|
|
296
|
+
});
|
|
297
|
+
expect(greeting).toContain("What's on your plate?");
|
|
298
|
+
expect(greeting).not.toContain("\n\n\n");
|
|
57
299
|
});
|
|
58
300
|
});
|
|
59
301
|
});
|
|
@@ -550,6 +550,63 @@ describe("browser_mode wiring through tool execution", () => {
|
|
|
550
550
|
expect(result.content).toContain("Remediation:");
|
|
551
551
|
});
|
|
552
552
|
|
|
553
|
+
// ── Transport-classified host-browser errors produce failover diagnostics ──
|
|
554
|
+
|
|
555
|
+
test("pinned extension with transport-classified host_browser error surfaces failover diagnostics", async () => {
|
|
556
|
+
// Simulate a structured transport error envelope from the host_browser
|
|
557
|
+
// dispatcher (e.g. { code: "unreachable", message: "..." }) that the
|
|
558
|
+
// extension-cdp-client now classifies as transport_error.
|
|
559
|
+
factoryThrowError = new CdpError(
|
|
560
|
+
"transport_error",
|
|
561
|
+
"Host browser not reachable",
|
|
562
|
+
{
|
|
563
|
+
attemptDiagnostics: [
|
|
564
|
+
{
|
|
565
|
+
candidateKind: "extension",
|
|
566
|
+
inclusionReason: "pinned mode: extension",
|
|
567
|
+
stage: "send",
|
|
568
|
+
errorCode: "transport_error",
|
|
569
|
+
errorMessage: "Host browser not reachable",
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const result = await executeBrowserNavigate(
|
|
576
|
+
{ url: "https://example.com", browser_mode: "extension" },
|
|
577
|
+
ctx,
|
|
578
|
+
);
|
|
579
|
+
expect(result.isError).toBe(true);
|
|
580
|
+
expect(result.content).toContain('Browser mode "extension" failed');
|
|
581
|
+
expect(result.content).toContain("extension: FAILED at send");
|
|
582
|
+
expect(result.content).toContain("Reason: Host browser not reachable");
|
|
583
|
+
expect(result.content).toContain("Remediation:");
|
|
584
|
+
expect(result.content).toContain("extension is installed and enabled");
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test("pinned extension with timeout transport error surfaces failover diagnostics in snapshot", async () => {
|
|
588
|
+
factoryThrowError = new CdpError("transport_error", "CDP call timed out", {
|
|
589
|
+
attemptDiagnostics: [
|
|
590
|
+
{
|
|
591
|
+
candidateKind: "extension",
|
|
592
|
+
inclusionReason: "pinned mode: extension",
|
|
593
|
+
stage: "send",
|
|
594
|
+
errorCode: "transport_error",
|
|
595
|
+
errorMessage: "CDP call timed out",
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const result = await executeBrowserSnapshot(
|
|
601
|
+
{ browser_mode: "extension" },
|
|
602
|
+
ctx,
|
|
603
|
+
);
|
|
604
|
+
expect(result.isError).toBe(true);
|
|
605
|
+
expect(result.content).toContain('Browser mode "extension" failed');
|
|
606
|
+
expect(result.content).toContain("extension: FAILED at send");
|
|
607
|
+
expect(result.content).toContain("Remediation:");
|
|
608
|
+
});
|
|
609
|
+
|
|
553
610
|
// ── Per-conversation sticky backend kind ─────────────────────────
|
|
554
611
|
|
|
555
612
|
test("auto-mode call after an explicit pin sticks to the pinned kind", async () => {
|