@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
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
-
|
|
6
|
-
import { HookManager } from "../hooks/manager.js";
|
|
7
|
-
|
|
8
|
-
let hooksDir: string;
|
|
9
|
-
|
|
10
|
-
function makeManifest(
|
|
11
|
-
name: string,
|
|
12
|
-
events: string[] = ["pre-llm-call"],
|
|
13
|
-
): string {
|
|
14
|
-
return JSON.stringify({
|
|
15
|
-
name,
|
|
16
|
-
description: `Test hook: ${name}`,
|
|
17
|
-
version: "1.0.0",
|
|
18
|
-
events,
|
|
19
|
-
script: "run.sh",
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function installHook(name: string, events: string[] = ["pre-llm-call"]): void {
|
|
24
|
-
const hookDir = join(hooksDir, name);
|
|
25
|
-
mkdirSync(hookDir, { recursive: true });
|
|
26
|
-
writeFileSync(join(hookDir, "hook.json"), makeManifest(name, events));
|
|
27
|
-
writeFileSync(join(hookDir, "run.sh"), "#!/bin/sh\nexit 0\n", {
|
|
28
|
-
mode: 0o755,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function writeConfig(hooks: Record<string, { enabled: boolean }>): void {
|
|
33
|
-
writeFileSync(
|
|
34
|
-
join(hooksDir, "config.json"),
|
|
35
|
-
JSON.stringify({ version: 1, hooks }),
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe("hooks watch mode", () => {
|
|
40
|
-
let manager: HookManager;
|
|
41
|
-
|
|
42
|
-
beforeEach(() => {
|
|
43
|
-
hooksDir = join(
|
|
44
|
-
tmpdir(),
|
|
45
|
-
`hooks-watch-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
46
|
-
);
|
|
47
|
-
mkdirSync(hooksDir, { recursive: true });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
afterEach(() => {
|
|
51
|
-
if (manager) manager.stopWatching();
|
|
52
|
-
rmSync(hooksDir, { recursive: true, force: true });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("reload() re-discovers hooks and rebuilds event index", () => {
|
|
56
|
-
installHook("hook-a", ["pre-llm-call"]);
|
|
57
|
-
writeConfig({ "hook-a": { enabled: true } });
|
|
58
|
-
|
|
59
|
-
manager = new HookManager();
|
|
60
|
-
// Initialize by discovering hooks from our temp dir
|
|
61
|
-
// We use a workaround since HookManager uses getHooksDir() internally
|
|
62
|
-
// Instead, test the reload behavior by checking the public API
|
|
63
|
-
const initialHooks = manager.getDiscoveredHooks();
|
|
64
|
-
expect(initialHooks).toHaveLength(0); // Not initialized yet
|
|
65
|
-
|
|
66
|
-
manager.initialize();
|
|
67
|
-
// After initialize, hooks depend on getHooksDir() which points elsewhere
|
|
68
|
-
// So we test reload conceptually: calling reload should not throw
|
|
69
|
-
expect(() => manager.reload()).not.toThrow();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("stopWatching() cleans up watcher and timer", () => {
|
|
73
|
-
manager = new HookManager();
|
|
74
|
-
// stopWatching before watch should not throw
|
|
75
|
-
expect(() => manager.stopWatching()).not.toThrow();
|
|
76
|
-
|
|
77
|
-
// watch on a non-existent dir should not throw
|
|
78
|
-
manager.watch();
|
|
79
|
-
expect(() => manager.stopWatching()).not.toThrow();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("watch() on non-existent directory does not throw", () => {
|
|
83
|
-
manager = new HookManager();
|
|
84
|
-
// The default hooks dir may not exist in test; should be safe
|
|
85
|
-
expect(() => manager.watch()).not.toThrow();
|
|
86
|
-
manager.stopWatching();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("reload() updates enabled hooks count", () => {
|
|
90
|
-
manager = new HookManager();
|
|
91
|
-
manager.initialize();
|
|
92
|
-
|
|
93
|
-
// First reload
|
|
94
|
-
const hooks1 = manager.getDiscoveredHooks();
|
|
95
|
-
|
|
96
|
-
// Reload should work without error
|
|
97
|
-
manager.reload();
|
|
98
|
-
const hooks2 = manager.getDiscoveredHooks();
|
|
99
|
-
|
|
100
|
-
// Both should return the same result (consistent state)
|
|
101
|
-
expect(hooks1.length).toBe(hooks2.length);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("multiple stopWatching() calls are idempotent", () => {
|
|
105
|
-
manager = new HookManager();
|
|
106
|
-
manager.watch();
|
|
107
|
-
manager.stopWatching();
|
|
108
|
-
manager.stopWatching();
|
|
109
|
-
manager.stopWatching();
|
|
110
|
-
// No errors thrown
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression test: recurring schedule notifications must not be
|
|
3
|
-
* deduplicated against prior firings of the same schedule.
|
|
4
|
-
*
|
|
5
|
-
* Before the fix, `schedule.complete` signals were emitted without a
|
|
6
|
-
* producer dedupeKey. The LLM decision engine would generate a stable
|
|
7
|
-
* key (e.g. `schedule:complete:<id>`) and `updateEventDedupeKey` would
|
|
8
|
-
* write it back to the event row. On the next firing, `checkDedupe`
|
|
9
|
-
* found the first row's stable key within the 1-hour window and
|
|
10
|
-
* silently blocked the notification.
|
|
11
|
-
*
|
|
12
|
-
* The fix: always supply a unique per-firing dedupeKey from the
|
|
13
|
-
* producer so `updateEventDedupeKey` is never called for schedule
|
|
14
|
-
* signals, and `checkDedupe` never finds a matching row.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
18
|
-
|
|
19
|
-
mock.module("../util/logger.js", () => ({
|
|
20
|
-
getLogger: () =>
|
|
21
|
-
new Proxy({} as Record<string, unknown>, {
|
|
22
|
-
get: () => () => {},
|
|
23
|
-
}),
|
|
24
|
-
truncateForLog: (value: string) => value,
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
import { getDb, initializeDb } from "../memory/db.js";
|
|
28
|
-
import { notificationEvents } from "../memory/schema.js";
|
|
29
|
-
import { runDeterministicChecks } from "../notifications/deterministic-checks.js";
|
|
30
|
-
import {
|
|
31
|
-
createEvent,
|
|
32
|
-
updateEventDedupeKey,
|
|
33
|
-
} from "../notifications/events-store.js";
|
|
34
|
-
import type { NotificationSignal } from "../notifications/signal.js";
|
|
35
|
-
import type { NotificationDecision } from "../notifications/types.js";
|
|
36
|
-
|
|
37
|
-
initializeDb();
|
|
38
|
-
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
// Clear notification events between tests for isolation
|
|
41
|
-
getDb().delete(notificationEvents).run();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
function makeSignal(
|
|
45
|
-
overrides?: Partial<NotificationSignal>,
|
|
46
|
-
): NotificationSignal {
|
|
47
|
-
return {
|
|
48
|
-
signalId: `sig-${crypto.randomUUID()}`,
|
|
49
|
-
createdAt: Date.now(),
|
|
50
|
-
sourceChannel: "scheduler",
|
|
51
|
-
sourceContextId: "schedule-123",
|
|
52
|
-
sourceEventName: "schedule.complete",
|
|
53
|
-
contextPayload: { scheduleId: "schedule-123", name: "Drink water" },
|
|
54
|
-
attentionHints: {
|
|
55
|
-
requiresAction: false,
|
|
56
|
-
urgency: "medium",
|
|
57
|
-
isAsyncBackground: true,
|
|
58
|
-
visibleInSourceNow: false,
|
|
59
|
-
},
|
|
60
|
-
...overrides,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function makeDecision(
|
|
65
|
-
overrides?: Partial<NotificationDecision>,
|
|
66
|
-
): NotificationDecision {
|
|
67
|
-
return {
|
|
68
|
-
shouldNotify: true,
|
|
69
|
-
selectedChannels: ["vellum"],
|
|
70
|
-
reasoningSummary: "Schedule completed",
|
|
71
|
-
renderedCopy: {
|
|
72
|
-
vellum: { title: "Reminder", body: "Time to drink water" },
|
|
73
|
-
},
|
|
74
|
-
dedupeKey: "schedule:complete:schedule-123",
|
|
75
|
-
confidence: 0.9,
|
|
76
|
-
fallbackUsed: false,
|
|
77
|
-
...overrides,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe("recurring schedule notification dedup", () => {
|
|
82
|
-
test("second firing is blocked when LLM stable key is written to first event row (the bug)", async () => {
|
|
83
|
-
// Simulate the BROKEN behavior: producer sends no dedupeKey,
|
|
84
|
-
// LLM generates a stable key, and updateEventDedupeKey writes it
|
|
85
|
-
// to the first event row.
|
|
86
|
-
|
|
87
|
-
const stableKey = "schedule:complete:schedule-123";
|
|
88
|
-
const firstId = crypto.randomUUID();
|
|
89
|
-
const secondId = crypto.randomUUID();
|
|
90
|
-
|
|
91
|
-
// First firing: create event with null dedupeKey, then backfill with LLM key
|
|
92
|
-
const firstSignal = makeSignal({ signalId: firstId });
|
|
93
|
-
createEvent({
|
|
94
|
-
id: firstSignal.signalId,
|
|
95
|
-
sourceEventName: "schedule.complete",
|
|
96
|
-
sourceChannel: "scheduler",
|
|
97
|
-
sourceContextId: "schedule-123",
|
|
98
|
-
attentionHints: firstSignal.attentionHints,
|
|
99
|
-
payload: firstSignal.contextPayload,
|
|
100
|
-
// No dedupeKey — this is the bug scenario
|
|
101
|
-
});
|
|
102
|
-
// LLM decision generates a stable key, pipeline writes it back
|
|
103
|
-
updateEventDedupeKey(firstSignal.signalId, stableKey);
|
|
104
|
-
|
|
105
|
-
// Second firing: new event, same schedule
|
|
106
|
-
const secondSignal = makeSignal({ signalId: secondId });
|
|
107
|
-
createEvent({
|
|
108
|
-
id: secondSignal.signalId,
|
|
109
|
-
sourceEventName: "schedule.complete",
|
|
110
|
-
sourceChannel: "scheduler",
|
|
111
|
-
sourceContextId: "schedule-123",
|
|
112
|
-
attentionHints: secondSignal.attentionHints,
|
|
113
|
-
payload: secondSignal.contextPayload,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// LLM generates the same stable key for the second firing
|
|
117
|
-
const decision = makeDecision({ dedupeKey: stableKey });
|
|
118
|
-
|
|
119
|
-
const result = await runDeterministicChecks(secondSignal, decision, {
|
|
120
|
-
connectedChannels: ["vellum"],
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// The second firing is BLOCKED — this is the bug
|
|
124
|
-
expect(result.passed).toBe(false);
|
|
125
|
-
expect(result.reason).toContain("Dedupe");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("second firing passes when producer supplies unique per-firing dedupeKey (the fix)", async () => {
|
|
129
|
-
const stableKey = "schedule:complete:schedule-123";
|
|
130
|
-
const firstId = crypto.randomUUID();
|
|
131
|
-
const secondId = crypto.randomUUID();
|
|
132
|
-
|
|
133
|
-
// First firing: producer supplies a timestamped dedupeKey
|
|
134
|
-
const firstSignal = makeSignal({ signalId: firstId });
|
|
135
|
-
createEvent({
|
|
136
|
-
id: firstSignal.signalId,
|
|
137
|
-
sourceEventName: "schedule.complete",
|
|
138
|
-
sourceChannel: "scheduler",
|
|
139
|
-
sourceContextId: "schedule-123",
|
|
140
|
-
attentionHints: firstSignal.attentionHints,
|
|
141
|
-
payload: firstSignal.contextPayload,
|
|
142
|
-
dedupeKey: `schedule:complete:schedule-123:${Date.now() - 60_000}`,
|
|
143
|
-
});
|
|
144
|
-
// updateEventDedupeKey is NOT called because params.dedupeKey is truthy
|
|
145
|
-
|
|
146
|
-
// Second firing: new event with its own unique timestamped key
|
|
147
|
-
const secondSignal = makeSignal({ signalId: secondId });
|
|
148
|
-
createEvent({
|
|
149
|
-
id: secondSignal.signalId,
|
|
150
|
-
sourceEventName: "schedule.complete",
|
|
151
|
-
sourceChannel: "scheduler",
|
|
152
|
-
sourceContextId: "schedule-123",
|
|
153
|
-
attentionHints: secondSignal.attentionHints,
|
|
154
|
-
payload: secondSignal.contextPayload,
|
|
155
|
-
dedupeKey: `schedule:complete:schedule-123:${Date.now()}`,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// LLM still generates a stable key — but no row in the DB has it
|
|
159
|
-
const decision = makeDecision({ dedupeKey: stableKey });
|
|
160
|
-
|
|
161
|
-
const result = await runDeterministicChecks(secondSignal, decision, {
|
|
162
|
-
connectedChannels: ["vellum"],
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// The second firing PASSES — the fix works
|
|
166
|
-
expect(result.passed).toBe(true);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("notify mode with timestamped producer keys is not blocked", async () => {
|
|
170
|
-
const stableKey = "schedule:notify:schedule-123";
|
|
171
|
-
const firstId = crypto.randomUUID();
|
|
172
|
-
const secondId = crypto.randomUUID();
|
|
173
|
-
|
|
174
|
-
// First firing
|
|
175
|
-
const firstSignal = makeSignal({
|
|
176
|
-
signalId: firstId,
|
|
177
|
-
sourceEventName: "schedule.notify",
|
|
178
|
-
});
|
|
179
|
-
createEvent({
|
|
180
|
-
id: firstSignal.signalId,
|
|
181
|
-
sourceEventName: "schedule.notify",
|
|
182
|
-
sourceChannel: "scheduler",
|
|
183
|
-
sourceContextId: "schedule-123",
|
|
184
|
-
attentionHints: firstSignal.attentionHints,
|
|
185
|
-
payload: firstSignal.contextPayload,
|
|
186
|
-
dedupeKey: `schedule:notify:schedule-123:${Date.now() - 60_000}`,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// Second firing
|
|
190
|
-
const secondSignal = makeSignal({
|
|
191
|
-
signalId: secondId,
|
|
192
|
-
sourceEventName: "schedule.notify",
|
|
193
|
-
});
|
|
194
|
-
createEvent({
|
|
195
|
-
id: secondSignal.signalId,
|
|
196
|
-
sourceEventName: "schedule.notify",
|
|
197
|
-
sourceChannel: "scheduler",
|
|
198
|
-
sourceContextId: "schedule-123",
|
|
199
|
-
attentionHints: secondSignal.attentionHints,
|
|
200
|
-
payload: secondSignal.contextPayload,
|
|
201
|
-
dedupeKey: `schedule:notify:schedule-123:${Date.now()}`,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// LLM generates stable key — no matching row
|
|
205
|
-
const decision = makeDecision({ dedupeKey: stableKey });
|
|
206
|
-
|
|
207
|
-
const result = await runDeterministicChecks(secondSignal, decision, {
|
|
208
|
-
connectedChannels: ["vellum"],
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
expect(result.passed).toBe(true);
|
|
212
|
-
});
|
|
213
|
-
});
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type { ScopeResolverInput } from "../oauth/scope-policy.js";
|
|
4
|
-
import { resolveScopes } from "../oauth/scope-policy.js";
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Helpers
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
/** Build a minimal scope resolver input for testing scope resolution. */
|
|
11
|
-
function makeProfile(
|
|
12
|
-
overrides: Partial<ScopeResolverInput> = {},
|
|
13
|
-
): ScopeResolverInput {
|
|
14
|
-
return {
|
|
15
|
-
service: "test-service",
|
|
16
|
-
defaultScopes: ["read", "write"],
|
|
17
|
-
scopePolicy: {
|
|
18
|
-
allowAdditionalScopes: false,
|
|
19
|
-
allowedOptionalScopes: [],
|
|
20
|
-
forbiddenScopes: [],
|
|
21
|
-
},
|
|
22
|
-
...overrides,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Tests
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
describe("resolveScopes", () => {
|
|
31
|
-
it("returns default scopes when no requestedScopes are provided", () => {
|
|
32
|
-
const profile = makeProfile({ defaultScopes: ["read", "write"] });
|
|
33
|
-
const result = resolveScopes(profile);
|
|
34
|
-
expect(result).toEqual({ ok: true, scopes: ["read", "write"] });
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("returns default scopes when requestedScopes is undefined", () => {
|
|
38
|
-
const profile = makeProfile({ defaultScopes: ["read", "write"] });
|
|
39
|
-
const result = resolveScopes(profile, undefined);
|
|
40
|
-
expect(result).toEqual({ ok: true, scopes: ["read", "write"] });
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("returns default scopes when requestedScopes is an empty array", () => {
|
|
44
|
-
const profile = makeProfile({ defaultScopes: ["read", "write"] });
|
|
45
|
-
const result = resolveScopes(profile, []);
|
|
46
|
-
expect(result).toEqual({ ok: true, scopes: ["read", "write"] });
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("rejects a forbidden scope with a clear error message", () => {
|
|
50
|
-
const profile = makeProfile({
|
|
51
|
-
scopePolicy: {
|
|
52
|
-
allowAdditionalScopes: true,
|
|
53
|
-
allowedOptionalScopes: ["admin"],
|
|
54
|
-
forbiddenScopes: ["delete"],
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
const result = resolveScopes(profile, ["delete"]);
|
|
58
|
-
expect(result.ok).toBe(false);
|
|
59
|
-
if (!result.ok) {
|
|
60
|
-
expect(result.error).toBe("Scope 'delete' is forbidden for test-service");
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("rejects additional scopes when allowAdditionalScopes is false", () => {
|
|
65
|
-
const profile = makeProfile({
|
|
66
|
-
defaultScopes: ["read", "write"],
|
|
67
|
-
scopePolicy: {
|
|
68
|
-
allowAdditionalScopes: false,
|
|
69
|
-
allowedOptionalScopes: [],
|
|
70
|
-
forbiddenScopes: [],
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
const result = resolveScopes(profile, ["admin"]);
|
|
74
|
-
expect(result.ok).toBe(false);
|
|
75
|
-
if (!result.ok) {
|
|
76
|
-
expect(result.error).toContain(
|
|
77
|
-
"Additional scopes are not allowed for test-service",
|
|
78
|
-
);
|
|
79
|
-
expect(result.allowedScopes).toEqual(["read", "write"]);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("rejects a scope not in allowedOptionalScopes even when allowAdditionalScopes is true", () => {
|
|
84
|
-
const profile = makeProfile({
|
|
85
|
-
defaultScopes: ["read"],
|
|
86
|
-
scopePolicy: {
|
|
87
|
-
allowAdditionalScopes: true,
|
|
88
|
-
allowedOptionalScopes: ["write"],
|
|
89
|
-
forbiddenScopes: [],
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
const result = resolveScopes(profile, ["admin"]);
|
|
93
|
-
expect(result.ok).toBe(false);
|
|
94
|
-
if (!result.ok) {
|
|
95
|
-
expect(result.error).toBe(
|
|
96
|
-
"Scope 'admin' is not in the allowed optional scopes for test-service",
|
|
97
|
-
);
|
|
98
|
-
expect(result.allowedScopes).toEqual(["read", "write"]);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("accepts an additional scope when it is in allowedOptionalScopes and allowAdditionalScopes is true", () => {
|
|
103
|
-
const profile = makeProfile({
|
|
104
|
-
defaultScopes: ["read"],
|
|
105
|
-
scopePolicy: {
|
|
106
|
-
allowAdditionalScopes: true,
|
|
107
|
-
allowedOptionalScopes: ["write", "admin"],
|
|
108
|
-
forbiddenScopes: [],
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
const result = resolveScopes(profile, ["write"]);
|
|
112
|
-
expect(result.ok).toBe(true);
|
|
113
|
-
if (result.ok) {
|
|
114
|
-
expect(result.scopes).toEqual(["read", "write"]);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("deduplicates scopes in the result", () => {
|
|
119
|
-
const profile = makeProfile({
|
|
120
|
-
defaultScopes: ["read", "write"],
|
|
121
|
-
scopePolicy: {
|
|
122
|
-
allowAdditionalScopes: true,
|
|
123
|
-
allowedOptionalScopes: ["admin"],
|
|
124
|
-
forbiddenScopes: [],
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
// Request duplicates of existing defaults and a new scope twice
|
|
128
|
-
const result = resolveScopes(profile, ["read", "write", "admin", "admin"]);
|
|
129
|
-
expect(result.ok).toBe(true);
|
|
130
|
-
if (result.ok) {
|
|
131
|
-
expect(result.scopes).toEqual(["read", "write", "admin"]);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("keeps requested scopes that are already in defaults without error", () => {
|
|
136
|
-
const profile = makeProfile({
|
|
137
|
-
defaultScopes: ["read", "write"],
|
|
138
|
-
scopePolicy: {
|
|
139
|
-
allowAdditionalScopes: false,
|
|
140
|
-
allowedOptionalScopes: [],
|
|
141
|
-
forbiddenScopes: [],
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
// Requesting only default scopes should succeed even when
|
|
145
|
-
// allowAdditionalScopes is false
|
|
146
|
-
const result = resolveScopes(profile, ["read", "write"]);
|
|
147
|
-
expect(result.ok).toBe(true);
|
|
148
|
-
if (result.ok) {
|
|
149
|
-
expect(result.scopes).toEqual(["read", "write"]);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("checks forbidden scopes before allowAdditionalScopes policy", () => {
|
|
154
|
-
const profile = makeProfile({
|
|
155
|
-
defaultScopes: ["read"],
|
|
156
|
-
scopePolicy: {
|
|
157
|
-
allowAdditionalScopes: true,
|
|
158
|
-
allowedOptionalScopes: [],
|
|
159
|
-
forbiddenScopes: ["destroy"],
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
const result = resolveScopes(profile, ["destroy"]);
|
|
163
|
-
expect(result.ok).toBe(false);
|
|
164
|
-
if (!result.ok) {
|
|
165
|
-
// Should be the forbidden error, not the "not in optional scopes" error
|
|
166
|
-
expect(result.error).toContain("forbidden");
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("returns a defensive copy of defaultScopes (not the same array reference)", () => {
|
|
171
|
-
const defaults = ["read", "write"];
|
|
172
|
-
const profile = makeProfile({ defaultScopes: defaults });
|
|
173
|
-
const result = resolveScopes(profile);
|
|
174
|
-
expect(result.ok).toBe(true);
|
|
175
|
-
if (result.ok) {
|
|
176
|
-
expect(result.scopes).toEqual(defaults);
|
|
177
|
-
expect(result.scopes).not.toBe(defaults);
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
const emitNotificationSignalMock = mock(async (_params: unknown) => {});
|
|
4
|
-
|
|
5
|
-
mock.module("../notifications/emit-signal.js", () => ({
|
|
6
|
-
emitNotificationSignal: (params: unknown) =>
|
|
7
|
-
emitNotificationSignalMock(params),
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
import { run } from "../config/bundled-skills/notifications/tools/send-notification.js";
|
|
11
|
-
|
|
12
|
-
describe("send-notification tool", () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
emitNotificationSignalMock.mockClear();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("emits a notification signal with normalized routing context", async () => {
|
|
18
|
-
const result = await run(
|
|
19
|
-
{
|
|
20
|
-
message: "Your verification code is 123456",
|
|
21
|
-
title: "Verification code",
|
|
22
|
-
urgency: "high",
|
|
23
|
-
conversation_id: "conv-override",
|
|
24
|
-
requires_action: true,
|
|
25
|
-
preferred_channels: ["vellum"],
|
|
26
|
-
deep_link_metadata: { conversationId: "conv-deeplink" },
|
|
27
|
-
dedupe_key: "voice-code-123456",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
workingDir: "/tmp",
|
|
31
|
-
conversationId: "conv-1",
|
|
32
|
-
assistantId: "ast-alpha",
|
|
33
|
-
trustClass: "guardian" as const,
|
|
34
|
-
},
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
expect(result.isError).toBe(false);
|
|
38
|
-
expect(emitNotificationSignalMock).toHaveBeenCalledTimes(1);
|
|
39
|
-
expect(emitNotificationSignalMock).toHaveBeenCalledWith({
|
|
40
|
-
sourceEventName: "user.send_notification",
|
|
41
|
-
sourceChannel: "assistant_tool",
|
|
42
|
-
sourceContextId: "conv-override",
|
|
43
|
-
attentionHints: {
|
|
44
|
-
requiresAction: true,
|
|
45
|
-
urgency: "high",
|
|
46
|
-
deadlineAt: undefined,
|
|
47
|
-
isAsyncBackground: false,
|
|
48
|
-
visibleInSourceNow: false,
|
|
49
|
-
},
|
|
50
|
-
contextPayload: {
|
|
51
|
-
requestedMessage: "Your verification code is 123456",
|
|
52
|
-
requestedByTool: "send_notification",
|
|
53
|
-
requestedByContextId: "conv-1",
|
|
54
|
-
requestedTitle: "Verification code",
|
|
55
|
-
requestedByConversationId: "conv-override",
|
|
56
|
-
preferredChannels: ["vellum"],
|
|
57
|
-
deepLinkMetadata: { conversationId: "conv-deeplink" },
|
|
58
|
-
},
|
|
59
|
-
dedupeKey: "voice-code-123456",
|
|
60
|
-
throwOnError: true,
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("returns an error when the notification pipeline throws", async () => {
|
|
65
|
-
emitNotificationSignalMock.mockImplementationOnce(async () => {
|
|
66
|
-
throw new Error("database unavailable");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const result = await run(
|
|
70
|
-
{ message: "test notification" },
|
|
71
|
-
{
|
|
72
|
-
workingDir: "/tmp",
|
|
73
|
-
conversationId: "conv-1",
|
|
74
|
-
assistantId: "ast-alpha",
|
|
75
|
-
trustClass: "guardian" as const,
|
|
76
|
-
},
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
expect(result.isError).toBe(true);
|
|
80
|
-
expect(result.content).toContain("database unavailable");
|
|
81
|
-
expect(emitNotificationSignalMock).toHaveBeenCalledTimes(1);
|
|
82
|
-
});
|
|
83
|
-
});
|