@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
|
@@ -86,6 +86,16 @@ let checkFnOverride:
|
|
|
86
86
|
/** Override for generateScopeOptions — when set, returns this value instead of the default. */
|
|
87
87
|
let scopeOptionsOverride: ScopeOption[] | undefined;
|
|
88
88
|
|
|
89
|
+
/** Override for getCachedAssessment — when set, returns this value. */
|
|
90
|
+
let cachedAssessmentOverride:
|
|
91
|
+
| {
|
|
92
|
+
riskLevel: string;
|
|
93
|
+
reason: string;
|
|
94
|
+
scopeOptions: Array<{ pattern: string; label: string }>;
|
|
95
|
+
matchType: string;
|
|
96
|
+
}
|
|
97
|
+
| undefined;
|
|
98
|
+
|
|
89
99
|
/** Spy on addRule to capture calls without replacing the real implementation. */
|
|
90
100
|
let addRuleSpy: ReturnType<typeof spyOn> | undefined;
|
|
91
101
|
|
|
@@ -127,6 +137,7 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
127
137
|
],
|
|
128
138
|
generateScopeOptions: () =>
|
|
129
139
|
scopeOptionsOverride ?? [{ label: "/tmp", scope: "/tmp" }],
|
|
140
|
+
getCachedAssessment: () => cachedAssessmentOverride,
|
|
130
141
|
}));
|
|
131
142
|
|
|
132
143
|
// Mock every export so downstream test files that dynamically import modules
|
|
@@ -196,6 +207,7 @@ describe("ToolExecutor allowedToolNames gating", () => {
|
|
|
196
207
|
getToolOverride = undefined;
|
|
197
208
|
checkResultOverride = undefined;
|
|
198
209
|
checkFnOverride = undefined;
|
|
210
|
+
cachedAssessmentOverride = undefined;
|
|
199
211
|
if (addRuleSpy) {
|
|
200
212
|
addRuleSpy.mockRestore();
|
|
201
213
|
addRuleSpy = undefined;
|
|
@@ -272,6 +284,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
272
284
|
getToolOverride = undefined;
|
|
273
285
|
checkResultOverride = undefined;
|
|
274
286
|
checkFnOverride = undefined;
|
|
287
|
+
cachedAssessmentOverride = undefined;
|
|
275
288
|
if (addRuleSpy) {
|
|
276
289
|
addRuleSpy.mockRestore();
|
|
277
290
|
addRuleSpy = undefined;
|
|
@@ -309,6 +322,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
309
322
|
expect(result.isError).toBe(false);
|
|
310
323
|
expect(lastCheckArgs).toBeDefined();
|
|
311
324
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
325
|
+
conversationId: "conversation-1",
|
|
312
326
|
executionContext: "conversation",
|
|
313
327
|
ephemeralRules: undefined,
|
|
314
328
|
executionTarget: "sandbox",
|
|
@@ -329,6 +343,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
329
343
|
expect(result.isError).toBe(false);
|
|
330
344
|
expect(lastCheckArgs).toBeDefined();
|
|
331
345
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
346
|
+
conversationId: "conversation-1",
|
|
332
347
|
executionContext: "conversation",
|
|
333
348
|
ephemeralRules: undefined,
|
|
334
349
|
});
|
|
@@ -362,6 +377,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
362
377
|
expect(result.isError).toBe(false);
|
|
363
378
|
expect(lastCheckArgs).toBeDefined();
|
|
364
379
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
380
|
+
conversationId: "conversation-1",
|
|
365
381
|
executionContext: "conversation",
|
|
366
382
|
ephemeralRules: undefined,
|
|
367
383
|
});
|
|
@@ -398,6 +414,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
398
414
|
expect(result.isError).toBe(false);
|
|
399
415
|
expect(lastCheckArgs).toBeDefined();
|
|
400
416
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
417
|
+
conversationId: "conversation-1",
|
|
401
418
|
executionContext: "conversation",
|
|
402
419
|
ephemeralRules: undefined,
|
|
403
420
|
executionTarget: "host",
|
|
@@ -430,6 +447,7 @@ describe("ToolExecutor policy context plumbing", () => {
|
|
|
430
447
|
expect(result.isError).toBe(false);
|
|
431
448
|
expect(lastCheckArgs).toBeDefined();
|
|
432
449
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
450
|
+
conversationId: "conversation-1",
|
|
433
451
|
executionContext: "conversation",
|
|
434
452
|
ephemeralRules: undefined,
|
|
435
453
|
executionTarget: undefined,
|
|
@@ -749,6 +767,7 @@ describe("ToolExecutor strict mode + high-risk integration (PR 25)", () => {
|
|
|
749
767
|
getToolOverride = undefined;
|
|
750
768
|
checkResultOverride = undefined;
|
|
751
769
|
checkFnOverride = undefined;
|
|
770
|
+
cachedAssessmentOverride = undefined;
|
|
752
771
|
if (addRuleSpy) {
|
|
753
772
|
addRuleSpy.mockRestore();
|
|
754
773
|
addRuleSpy = undefined;
|
|
@@ -901,6 +920,7 @@ describe("ToolExecutor strict mode + high-risk integration (PR 25)", () => {
|
|
|
901
920
|
|
|
902
921
|
expect(lastCheckArgs).toBeDefined();
|
|
903
922
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
923
|
+
conversationId: "conversation-1",
|
|
904
924
|
executionContext: "conversation",
|
|
905
925
|
ephemeralRules: undefined,
|
|
906
926
|
executionTarget: "sandbox",
|
|
@@ -1085,6 +1105,7 @@ describe("ToolExecutor strict mode + high-risk integration (PR 25)", () => {
|
|
|
1085
1105
|
|
|
1086
1106
|
expect(lastCheckArgs).toBeDefined();
|
|
1087
1107
|
expect(lastCheckArgs!.policyContext).toEqual({
|
|
1108
|
+
conversationId: "conversation-1",
|
|
1088
1109
|
executionContext: "conversation",
|
|
1089
1110
|
ephemeralRules: undefined,
|
|
1090
1111
|
executionTarget: "sandbox",
|
|
@@ -1252,6 +1273,7 @@ describe("ToolExecutor baseline: allow rule auto-allows file_edit guardian perso
|
|
|
1252
1273
|
getToolOverride = undefined;
|
|
1253
1274
|
checkResultOverride = undefined;
|
|
1254
1275
|
checkFnOverride = undefined;
|
|
1276
|
+
cachedAssessmentOverride = undefined;
|
|
1255
1277
|
if (addRuleSpy) {
|
|
1256
1278
|
addRuleSpy.mockRestore();
|
|
1257
1279
|
addRuleSpy = undefined;
|
|
@@ -1369,6 +1391,7 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1369
1391
|
getToolOverride = undefined;
|
|
1370
1392
|
checkResultOverride = undefined;
|
|
1371
1393
|
checkFnOverride = undefined;
|
|
1394
|
+
cachedAssessmentOverride = undefined;
|
|
1372
1395
|
promptCalled = false;
|
|
1373
1396
|
if (addRuleSpy) {
|
|
1374
1397
|
addRuleSpy.mockRestore();
|
|
@@ -2124,6 +2147,7 @@ describe("ToolExecutor persistent-allow lifecycle", () => {
|
|
|
2124
2147
|
getToolOverride = undefined;
|
|
2125
2148
|
checkResultOverride = undefined;
|
|
2126
2149
|
checkFnOverride = undefined;
|
|
2150
|
+
cachedAssessmentOverride = undefined;
|
|
2127
2151
|
if (addRuleSpy) {
|
|
2128
2152
|
addRuleSpy.mockRestore();
|
|
2129
2153
|
addRuleSpy = undefined;
|
|
@@ -2302,3 +2326,120 @@ describe("integration regressions — prompt payload (PR 11)", () => {
|
|
|
2302
2326
|
expect(capturedScopes![0]).toHaveProperty("scope");
|
|
2303
2327
|
});
|
|
2304
2328
|
});
|
|
2329
|
+
|
|
2330
|
+
// ---------------------------------------------------------------------------
|
|
2331
|
+
// Risk metadata on ToolExecutionResult (PR 5 — scope-ladder-v1)
|
|
2332
|
+
// ---------------------------------------------------------------------------
|
|
2333
|
+
|
|
2334
|
+
describe("ToolExecutionResult includes risk metadata from classifier assessment", () => {
|
|
2335
|
+
beforeEach(() => {
|
|
2336
|
+
fakeToolResult = { content: "ok", isError: false };
|
|
2337
|
+
lastCheckArgs = undefined;
|
|
2338
|
+
getToolOverride = undefined;
|
|
2339
|
+
checkResultOverride = undefined;
|
|
2340
|
+
checkFnOverride = undefined;
|
|
2341
|
+
cachedAssessmentOverride = undefined;
|
|
2342
|
+
if (addRuleSpy) {
|
|
2343
|
+
addRuleSpy.mockRestore();
|
|
2344
|
+
addRuleSpy = undefined;
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
|
|
2348
|
+
test("auto-approved tool result includes risk metadata when classifier assessment exists", async () => {
|
|
2349
|
+
cachedAssessmentOverride = {
|
|
2350
|
+
riskLevel: "medium",
|
|
2351
|
+
reason: "Writes to a file outside the workspace",
|
|
2352
|
+
scopeOptions: [
|
|
2353
|
+
{ pattern: "file_write:/tmp/test.txt", label: "This file only" },
|
|
2354
|
+
{ pattern: "file_write:/tmp/**", label: "Anything in tmp/" },
|
|
2355
|
+
],
|
|
2356
|
+
matchType: "registry",
|
|
2357
|
+
};
|
|
2358
|
+
|
|
2359
|
+
const executor = new ToolExecutor(makePrompter());
|
|
2360
|
+
const result = await executor.execute(
|
|
2361
|
+
"file_read",
|
|
2362
|
+
{ path: "README.md" },
|
|
2363
|
+
makeContext(),
|
|
2364
|
+
);
|
|
2365
|
+
|
|
2366
|
+
expect(result.isError).toBe(false);
|
|
2367
|
+
expect(result.riskLevel).toBe("medium");
|
|
2368
|
+
expect(result.riskReason).toBe("Writes to a file outside the workspace");
|
|
2369
|
+
expect(result.riskScopeOptions).toEqual([
|
|
2370
|
+
{ pattern: "file_write:/tmp/test.txt", label: "This file only" },
|
|
2371
|
+
{ pattern: "file_write:/tmp/**", label: "Anything in tmp/" },
|
|
2372
|
+
]);
|
|
2373
|
+
});
|
|
2374
|
+
|
|
2375
|
+
test("tool result omits risk metadata when no classifier assessment exists (e.g. MCP tools)", async () => {
|
|
2376
|
+
// cachedAssessmentOverride is undefined (no classifier ran)
|
|
2377
|
+
const executor = new ToolExecutor(makePrompter());
|
|
2378
|
+
const result = await executor.execute(
|
|
2379
|
+
"file_read",
|
|
2380
|
+
{ path: "README.md" },
|
|
2381
|
+
makeContext(),
|
|
2382
|
+
);
|
|
2383
|
+
|
|
2384
|
+
expect(result.isError).toBe(false);
|
|
2385
|
+
expect(result.riskLevel).toBeUndefined();
|
|
2386
|
+
expect(result.riskReason).toBeUndefined();
|
|
2387
|
+
expect(result.riskScopeOptions).toBeUndefined();
|
|
2388
|
+
});
|
|
2389
|
+
|
|
2390
|
+
test("denied tool result includes risk metadata", async () => {
|
|
2391
|
+
checkResultOverride = {
|
|
2392
|
+
decision: "deny",
|
|
2393
|
+
reason: "Blocked by deny rule",
|
|
2394
|
+
};
|
|
2395
|
+
cachedAssessmentOverride = {
|
|
2396
|
+
riskLevel: "high",
|
|
2397
|
+
reason: "Recursive force delete",
|
|
2398
|
+
scopeOptions: [{ pattern: "bash:rm -rf*", label: "rm -rf commands" }],
|
|
2399
|
+
matchType: "registry",
|
|
2400
|
+
};
|
|
2401
|
+
|
|
2402
|
+
const executor = new ToolExecutor(makePrompter());
|
|
2403
|
+
const result = await executor.execute(
|
|
2404
|
+
"bash",
|
|
2405
|
+
{ command: "rm -rf /" },
|
|
2406
|
+
makeContext(),
|
|
2407
|
+
);
|
|
2408
|
+
|
|
2409
|
+
expect(result.isError).toBe(true);
|
|
2410
|
+
expect(result.riskLevel).toBe("high");
|
|
2411
|
+
expect(result.riskReason).toBe("Recursive force delete");
|
|
2412
|
+
expect(result.riskScopeOptions).toEqual([
|
|
2413
|
+
{ pattern: "bash:rm -rf*", label: "rm -rf commands" },
|
|
2414
|
+
]);
|
|
2415
|
+
});
|
|
2416
|
+
|
|
2417
|
+
test("prompted-then-approved tool result includes risk metadata", async () => {
|
|
2418
|
+
checkResultOverride = {
|
|
2419
|
+
decision: "prompt",
|
|
2420
|
+
reason: "Medium risk: requires approval",
|
|
2421
|
+
};
|
|
2422
|
+
cachedAssessmentOverride = {
|
|
2423
|
+
riskLevel: "medium",
|
|
2424
|
+
reason: "Package manager installation",
|
|
2425
|
+
scopeOptions: [
|
|
2426
|
+
{ pattern: "bash:npm install*", label: "npm install commands" },
|
|
2427
|
+
{ pattern: "bash:npm*", label: "All npm commands" },
|
|
2428
|
+
],
|
|
2429
|
+
matchType: "registry",
|
|
2430
|
+
};
|
|
2431
|
+
|
|
2432
|
+
const executor = new ToolExecutor(makePrompter());
|
|
2433
|
+
const result = await executor.execute(
|
|
2434
|
+
"bash",
|
|
2435
|
+
{ command: "npm install lodash" },
|
|
2436
|
+
makeContext(),
|
|
2437
|
+
);
|
|
2438
|
+
|
|
2439
|
+
expect(result.isError).toBe(false);
|
|
2440
|
+
expect(result.content).toBe("ok");
|
|
2441
|
+
expect(result.riskLevel).toBe("medium");
|
|
2442
|
+
expect(result.riskReason).toBe("Package manager installation");
|
|
2443
|
+
expect(result.riskScopeOptions).toHaveLength(2);
|
|
2444
|
+
});
|
|
2445
|
+
});
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `toolResultTruncate` plugin pipeline (PR 17).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - The default terminal delegates to `truncateToolResultText`, producing
|
|
6
|
+
* byte-for-byte identical output to calling the helper directly across
|
|
7
|
+
* short, long, and newline-bounded inputs (property-style).
|
|
8
|
+
* - The pipeline routes through `runPipeline` with the
|
|
9
|
+
* `DEFAULT_TIMEOUTS.toolResultTruncate` budget and returns a
|
|
10
|
+
* `{ content, truncated }` pair whose `truncated` flag matches whether
|
|
11
|
+
* the content actually changed.
|
|
12
|
+
* - Plugins registered alongside the default can short-circuit or decorate
|
|
13
|
+
* the terminal's output.
|
|
14
|
+
* - A user plugin registered AFTER the default still runs — the default
|
|
15
|
+
* plugin's middleware is a passthrough, so the onion composition cannot
|
|
16
|
+
* shadow late-registered middleware.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
MIN_KEEP_CHARS,
|
|
23
|
+
truncateToolResultText,
|
|
24
|
+
TRUNCATION_SUFFIX,
|
|
25
|
+
} from "../context/tool-result-truncation.js";
|
|
26
|
+
import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
|
|
27
|
+
import {
|
|
28
|
+
defaultToolResultTruncatePlugin,
|
|
29
|
+
defaultToolResultTruncateTerminal,
|
|
30
|
+
} from "../plugins/defaults/tool-result-truncate.js";
|
|
31
|
+
import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
|
|
32
|
+
import {
|
|
33
|
+
getMiddlewaresFor,
|
|
34
|
+
registerPlugin,
|
|
35
|
+
resetPluginRegistryForTests,
|
|
36
|
+
} from "../plugins/registry.js";
|
|
37
|
+
import {
|
|
38
|
+
type Middleware,
|
|
39
|
+
type ToolResultTruncateArgs,
|
|
40
|
+
type ToolResultTruncateResult,
|
|
41
|
+
type TurnContext,
|
|
42
|
+
} from "../plugins/types.js";
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Fixtures
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const trust: TrustContext = {
|
|
49
|
+
sourceChannel: "vellum",
|
|
50
|
+
trustClass: "guardian",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function makeCtx(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
54
|
+
return {
|
|
55
|
+
requestId: "req-test",
|
|
56
|
+
conversationId: "conv-test",
|
|
57
|
+
turnIndex: 0,
|
|
58
|
+
trust,
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("toolResultTruncate pipeline", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
resetPluginRegistryForTests();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
// Default terminal — isolated (no pipeline runner)
|
|
70
|
+
// -------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
describe("default terminal", () => {
|
|
73
|
+
test("passes short content through unchanged with truncated=false", () => {
|
|
74
|
+
const content = "hello world";
|
|
75
|
+
const result = defaultToolResultTruncateTerminal({
|
|
76
|
+
content,
|
|
77
|
+
maxChars: 100,
|
|
78
|
+
});
|
|
79
|
+
expect(result.content).toBe(content);
|
|
80
|
+
expect(result.truncated).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("truncates oversize content and reports truncated=true", () => {
|
|
84
|
+
const content = "a".repeat(10_000);
|
|
85
|
+
const maxChars = 5_000;
|
|
86
|
+
const expected = truncateToolResultText(content, maxChars);
|
|
87
|
+
const result = defaultToolResultTruncateTerminal({ content, maxChars });
|
|
88
|
+
expect(result.content).toBe(expected);
|
|
89
|
+
expect(result.truncated).toBe(true);
|
|
90
|
+
expect(result.content).toContain(TRUNCATION_SUFFIX);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("snaps to newline boundary identically to truncateToolResultText", () => {
|
|
94
|
+
const lines = Array.from(
|
|
95
|
+
{ length: 1_000 },
|
|
96
|
+
(_, i) => `line ${i}: ${"x".repeat(20)}`,
|
|
97
|
+
).join("\n");
|
|
98
|
+
const maxChars = 5_000;
|
|
99
|
+
const expected = truncateToolResultText(lines, maxChars);
|
|
100
|
+
const result = defaultToolResultTruncateTerminal({
|
|
101
|
+
content: lines,
|
|
102
|
+
maxChars,
|
|
103
|
+
});
|
|
104
|
+
expect(result.content).toBe(expected);
|
|
105
|
+
expect(result.truncated).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("returns truncated=false when effectiveMax keeps the full text (maxChars < MIN_KEEP_CHARS case)", () => {
|
|
109
|
+
const textLength = MIN_KEEP_CHARS - TRUNCATION_SUFFIX.length - 10;
|
|
110
|
+
const content = "a".repeat(textLength);
|
|
111
|
+
const maxChars = 100;
|
|
112
|
+
const expected = truncateToolResultText(content, maxChars);
|
|
113
|
+
const result = defaultToolResultTruncateTerminal({ content, maxChars });
|
|
114
|
+
// Helper returns the original text unchanged in this case.
|
|
115
|
+
expect(expected).toBe(content);
|
|
116
|
+
expect(result.content).toBe(content);
|
|
117
|
+
expect(result.truncated).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// -------------------------------------------------------------------------
|
|
122
|
+
// End-to-end: default plugin routed through runPipeline
|
|
123
|
+
// -------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
describe("runPipeline with the default plugin registered", () => {
|
|
126
|
+
async function runDefault(
|
|
127
|
+
content: string,
|
|
128
|
+
maxChars: number,
|
|
129
|
+
): Promise<ToolResultTruncateResult> {
|
|
130
|
+
registerPlugin(defaultToolResultTruncatePlugin);
|
|
131
|
+
const middlewares = getMiddlewaresFor("toolResultTruncate");
|
|
132
|
+
return runPipeline<ToolResultTruncateArgs, ToolResultTruncateResult>(
|
|
133
|
+
"toolResultTruncate",
|
|
134
|
+
middlewares,
|
|
135
|
+
async (args) => defaultToolResultTruncateTerminal(args),
|
|
136
|
+
{ content, maxChars },
|
|
137
|
+
makeCtx(),
|
|
138
|
+
DEFAULT_TIMEOUTS.toolResultTruncate,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
test("short content round-trip matches truncateToolResultText", async () => {
|
|
143
|
+
const content = "quick brown fox";
|
|
144
|
+
const maxChars = 200;
|
|
145
|
+
const expected = truncateToolResultText(content, maxChars);
|
|
146
|
+
const result = await runDefault(content, maxChars);
|
|
147
|
+
expect(result.content).toBe(expected);
|
|
148
|
+
expect(result.truncated).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("long content round-trip matches truncateToolResultText", async () => {
|
|
152
|
+
const content = "z".repeat(50_000);
|
|
153
|
+
const maxChars = 10_000;
|
|
154
|
+
const expected = truncateToolResultText(content, maxChars);
|
|
155
|
+
const result = await runDefault(content, maxChars);
|
|
156
|
+
expect(result.content).toBe(expected);
|
|
157
|
+
expect(result.truncated).toBe(true);
|
|
158
|
+
expect(result.content).toContain(TRUNCATION_SUFFIX);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("newline-bounded content round-trip matches truncateToolResultText", async () => {
|
|
162
|
+
const lines = Array.from(
|
|
163
|
+
{ length: 500 },
|
|
164
|
+
(_, i) => `line ${i}: ${"y".repeat(40)}`,
|
|
165
|
+
).join("\n");
|
|
166
|
+
const maxChars = 4_000;
|
|
167
|
+
const expected = truncateToolResultText(lines, maxChars);
|
|
168
|
+
const result = await runDefault(lines, maxChars);
|
|
169
|
+
expect(result.content).toBe(expected);
|
|
170
|
+
expect(result.truncated).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("property test: default pipeline output equals direct truncateToolResultText across varied inputs", async () => {
|
|
174
|
+
// Deterministic pseudo-random over a fixed seed — bun's test runner
|
|
175
|
+
// doesn't ship a property-test library, so we hand-roll a tiny LCG
|
|
176
|
+
// that produces enough spread for a meaningful regression signal
|
|
177
|
+
// without introducing a dependency.
|
|
178
|
+
let seed = 0xc0ffee;
|
|
179
|
+
const rand = () => {
|
|
180
|
+
seed = (seed * 1664525 + 1013904223) & 0xffffffff;
|
|
181
|
+
return (seed >>> 0) / 0x100000000;
|
|
182
|
+
};
|
|
183
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz \n";
|
|
184
|
+
|
|
185
|
+
const cases: Array<{ content: string; maxChars: number }> = [];
|
|
186
|
+
for (let i = 0; i < 40; i++) {
|
|
187
|
+
// Lengths span short, boundary, and long relative to the maxChars
|
|
188
|
+
// budget so the property covers pass-through, newline-snap, and
|
|
189
|
+
// pure tail-drop paths.
|
|
190
|
+
const length = Math.floor(rand() * 20_000);
|
|
191
|
+
let content = "";
|
|
192
|
+
for (let j = 0; j < length; j++) {
|
|
193
|
+
content += alphabet[Math.floor(rand() * alphabet.length)];
|
|
194
|
+
}
|
|
195
|
+
const maxChars = 1_000 + Math.floor(rand() * 9_000);
|
|
196
|
+
cases.push({ content, maxChars });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Register once outside the loop — registry is reset in `beforeEach`,
|
|
200
|
+
// so the per-case reset lives in the loop instead.
|
|
201
|
+
for (const { content, maxChars } of cases) {
|
|
202
|
+
resetPluginRegistryForTests();
|
|
203
|
+
registerPlugin(defaultToolResultTruncatePlugin);
|
|
204
|
+
const middlewares = getMiddlewaresFor("toolResultTruncate");
|
|
205
|
+
const result = await runPipeline<
|
|
206
|
+
ToolResultTruncateArgs,
|
|
207
|
+
ToolResultTruncateResult
|
|
208
|
+
>(
|
|
209
|
+
"toolResultTruncate",
|
|
210
|
+
middlewares,
|
|
211
|
+
async (args) => defaultToolResultTruncateTerminal(args),
|
|
212
|
+
{ content, maxChars },
|
|
213
|
+
makeCtx(),
|
|
214
|
+
DEFAULT_TIMEOUTS.toolResultTruncate,
|
|
215
|
+
);
|
|
216
|
+
const expected = truncateToolResultText(content, maxChars);
|
|
217
|
+
expect(result.content).toBe(expected);
|
|
218
|
+
expect(result.truncated).toBe(expected !== content);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// -------------------------------------------------------------------------
|
|
224
|
+
// Middleware composition — an outer plugin can intercept / transform
|
|
225
|
+
// -------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
describe("middleware composition", () => {
|
|
228
|
+
test("an outer plugin can short-circuit the default with its own content", async () => {
|
|
229
|
+
const shortCircuit: Middleware<
|
|
230
|
+
ToolResultTruncateArgs,
|
|
231
|
+
ToolResultTruncateResult
|
|
232
|
+
> = async (_args, _next, _ctx) => {
|
|
233
|
+
return { content: "SUMMARY", truncated: true };
|
|
234
|
+
};
|
|
235
|
+
registerPlugin({
|
|
236
|
+
manifest: {
|
|
237
|
+
name: "short-circuit",
|
|
238
|
+
version: "1.0.0",
|
|
239
|
+
requires: {
|
|
240
|
+
pluginRuntime: "v1",
|
|
241
|
+
toolResultTruncateApi: "v1",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
middleware: { toolResultTruncate: shortCircuit },
|
|
245
|
+
});
|
|
246
|
+
registerPlugin(defaultToolResultTruncatePlugin);
|
|
247
|
+
|
|
248
|
+
const middlewares = getMiddlewaresFor("toolResultTruncate");
|
|
249
|
+
const result = await runPipeline<
|
|
250
|
+
ToolResultTruncateArgs,
|
|
251
|
+
ToolResultTruncateResult
|
|
252
|
+
>(
|
|
253
|
+
"toolResultTruncate",
|
|
254
|
+
middlewares,
|
|
255
|
+
async (args) => defaultToolResultTruncateTerminal(args),
|
|
256
|
+
{ content: "a".repeat(10_000), maxChars: 5_000 },
|
|
257
|
+
makeCtx(),
|
|
258
|
+
DEFAULT_TIMEOUTS.toolResultTruncate,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
expect(result.content).toBe("SUMMARY");
|
|
262
|
+
expect(result.truncated).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("an outer plugin can observe and mutate the default's output", async () => {
|
|
266
|
+
const prefixer: Middleware<
|
|
267
|
+
ToolResultTruncateArgs,
|
|
268
|
+
ToolResultTruncateResult
|
|
269
|
+
> = async (args, next, _ctx) => {
|
|
270
|
+
const inner = await next(args);
|
|
271
|
+
return { ...inner, content: `[wrapped] ${inner.content}` };
|
|
272
|
+
};
|
|
273
|
+
registerPlugin({
|
|
274
|
+
manifest: {
|
|
275
|
+
name: "prefixer",
|
|
276
|
+
version: "1.0.0",
|
|
277
|
+
requires: {
|
|
278
|
+
pluginRuntime: "v1",
|
|
279
|
+
toolResultTruncateApi: "v1",
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
middleware: { toolResultTruncate: prefixer },
|
|
283
|
+
});
|
|
284
|
+
registerPlugin(defaultToolResultTruncatePlugin);
|
|
285
|
+
|
|
286
|
+
const middlewares = getMiddlewaresFor("toolResultTruncate");
|
|
287
|
+
const content = "hello";
|
|
288
|
+
const result = await runPipeline<
|
|
289
|
+
ToolResultTruncateArgs,
|
|
290
|
+
ToolResultTruncateResult
|
|
291
|
+
>(
|
|
292
|
+
"toolResultTruncate",
|
|
293
|
+
middlewares,
|
|
294
|
+
async (args) => defaultToolResultTruncateTerminal(args),
|
|
295
|
+
{ content, maxChars: 100 },
|
|
296
|
+
makeCtx(),
|
|
297
|
+
DEFAULT_TIMEOUTS.toolResultTruncate,
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
expect(result.content).toBe(`[wrapped] ${content}`);
|
|
301
|
+
expect(result.truncated).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("user plugin registered AFTER the default still runs (no shadowing)", async () => {
|
|
305
|
+
// Production registration order: defaults load first via the
|
|
306
|
+
// side-effect imports in `defaults/index.ts`, then user plugins
|
|
307
|
+
// register on top (via `bootstrapPlugins()` or hot-reload). The
|
|
308
|
+
// user's middleware ends up at a deeper onion layer than the
|
|
309
|
+
// default. If the default's middleware were to bypass `next` and
|
|
310
|
+
// call the terminal directly, the user middleware would never run
|
|
311
|
+
// — this test guards against that regression.
|
|
312
|
+
registerPlugin(defaultToolResultTruncatePlugin);
|
|
313
|
+
|
|
314
|
+
let userMiddlewareRan = false;
|
|
315
|
+
const userMiddleware: Middleware<
|
|
316
|
+
ToolResultTruncateArgs,
|
|
317
|
+
ToolResultTruncateResult
|
|
318
|
+
> = async (args, next) => {
|
|
319
|
+
userMiddlewareRan = true;
|
|
320
|
+
return next(args);
|
|
321
|
+
};
|
|
322
|
+
registerPlugin({
|
|
323
|
+
manifest: {
|
|
324
|
+
name: "late-user-plugin",
|
|
325
|
+
version: "0.0.1",
|
|
326
|
+
requires: {
|
|
327
|
+
pluginRuntime: "v1",
|
|
328
|
+
toolResultTruncateApi: "v1",
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
middleware: { toolResultTruncate: userMiddleware },
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const middlewares = getMiddlewaresFor("toolResultTruncate");
|
|
335
|
+
const content = "a".repeat(10_000);
|
|
336
|
+
const maxChars = 5_000;
|
|
337
|
+
const result = await runPipeline<
|
|
338
|
+
ToolResultTruncateArgs,
|
|
339
|
+
ToolResultTruncateResult
|
|
340
|
+
>(
|
|
341
|
+
"toolResultTruncate",
|
|
342
|
+
middlewares,
|
|
343
|
+
async (args) => defaultToolResultTruncateTerminal(args),
|
|
344
|
+
{ content, maxChars },
|
|
345
|
+
makeCtx(),
|
|
346
|
+
DEFAULT_TIMEOUTS.toolResultTruncate,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
expect(userMiddlewareRan).toBe(true);
|
|
350
|
+
// Terminal still runs after the user passthrough, so output matches
|
|
351
|
+
// the direct helper.
|
|
352
|
+
expect(result.content).toBe(truncateToolResultText(content, maxChars));
|
|
353
|
+
expect(result.truncated).toBe(true);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|