@vellumai/assistant 0.6.4 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit-propagation tests for Slack `message.changed` events.
|
|
3
|
+
*
|
|
4
|
+
* Validates that the edit intercept stage:
|
|
5
|
+
* - Updates `messages.content` and stamps `slackMeta.editedAt` when the
|
|
6
|
+
* original message can be located.
|
|
7
|
+
* - Is idempotent across successive edits (subsequent edits keep updating).
|
|
8
|
+
* - Treats missing-target edits as a silent no-op (no throw, no row change).
|
|
9
|
+
*/
|
|
10
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import { eq } from "drizzle-orm";
|
|
13
|
+
|
|
14
|
+
mock.module("../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { addMessage } from "../memory/conversation-crud.js";
|
|
22
|
+
import { getDb, initializeDb } from "../memory/db.js";
|
|
23
|
+
import { linkMessage, recordInbound } from "../memory/delivery-crud.js";
|
|
24
|
+
import { messages } from "../memory/schema.js";
|
|
25
|
+
import { readSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
|
|
26
|
+
import { handleEditIntercept } from "../runtime/routes/inbound-stages/edit-intercept.js";
|
|
27
|
+
|
|
28
|
+
initializeDb();
|
|
29
|
+
|
|
30
|
+
function resetTables(): void {
|
|
31
|
+
const db = getDb();
|
|
32
|
+
db.run("DELETE FROM channel_inbound_events");
|
|
33
|
+
db.run("DELETE FROM messages");
|
|
34
|
+
db.run("DELETE FROM conversation_keys");
|
|
35
|
+
db.run("DELETE FROM conversations");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface SeededFixture {
|
|
39
|
+
conversationId: string;
|
|
40
|
+
messageId: string;
|
|
41
|
+
channelTs: string;
|
|
42
|
+
conversationExternalId: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Seed a Slack message in a fresh conversation. Mirrors what the new-message
|
|
47
|
+
* pipeline does at runtime: `recordInbound` writes the channel_inbound_events
|
|
48
|
+
* row (storing `sourceMessageId = ts`), `addMessage` writes the user message,
|
|
49
|
+
* and `linkMessage` connects them so edit lookups succeed.
|
|
50
|
+
*
|
|
51
|
+
* Note: the gateway sets `externalMessageId = client_msg_id ?? ts` for new
|
|
52
|
+
* Slack messages, so this fixture mirrors a message where `client_msg_id`
|
|
53
|
+
* equals the `ts` (i.e. the simplest case). The lookup mechanism keys on
|
|
54
|
+
* `sourceMessageId`, which always carries the `ts`, so the test exercises
|
|
55
|
+
* the same path that production hits regardless of `client_msg_id` presence.
|
|
56
|
+
*/
|
|
57
|
+
async function seedSlackMessage(opts: {
|
|
58
|
+
conversationExternalId: string;
|
|
59
|
+
channelTs: string;
|
|
60
|
+
initialContent: string;
|
|
61
|
+
}): Promise<SeededFixture> {
|
|
62
|
+
const { conversationExternalId, channelTs, initialContent } = opts;
|
|
63
|
+
|
|
64
|
+
const inboundResult = recordInbound(
|
|
65
|
+
"slack",
|
|
66
|
+
conversationExternalId,
|
|
67
|
+
channelTs,
|
|
68
|
+
{
|
|
69
|
+
sourceMessageId: channelTs,
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const inserted = await addMessage(
|
|
74
|
+
inboundResult.conversationId,
|
|
75
|
+
"user",
|
|
76
|
+
initialContent,
|
|
77
|
+
{ userMessageChannel: "slack" },
|
|
78
|
+
{ skipIndexing: true },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
linkMessage(inboundResult.eventId, inserted.id);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
conversationId: inboundResult.conversationId,
|
|
85
|
+
messageId: inserted.id,
|
|
86
|
+
channelTs,
|
|
87
|
+
conversationExternalId,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readMessageRow(messageId: string): {
|
|
92
|
+
content: string;
|
|
93
|
+
metadata: string | null;
|
|
94
|
+
} {
|
|
95
|
+
const db = getDb();
|
|
96
|
+
const row = db
|
|
97
|
+
.select({ content: messages.content, metadata: messages.metadata })
|
|
98
|
+
.from(messages)
|
|
99
|
+
.where(eq(messages.id, messageId))
|
|
100
|
+
.get();
|
|
101
|
+
if (!row) {
|
|
102
|
+
throw new Error(`message ${messageId} not found`);
|
|
103
|
+
}
|
|
104
|
+
return { content: row.content, metadata: row.metadata };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let editEventCounter = 0;
|
|
108
|
+
function nextEditEventId(): string {
|
|
109
|
+
editEventCounter += 1;
|
|
110
|
+
return `edit-event-${Date.now()}-${editEventCounter}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
describe("Slack edit propagation", () => {
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
resetTables();
|
|
116
|
+
editEventCounter = 0;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("updates content and stamps slackMeta.editedAt when original is found", async () => {
|
|
120
|
+
const seeded = await seedSlackMessage({
|
|
121
|
+
conversationExternalId: "C0123CHANNEL",
|
|
122
|
+
channelTs: "1234.5678",
|
|
123
|
+
initialContent: "original text",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const before = readMessageRow(seeded.messageId);
|
|
127
|
+
expect(before.content).toBe("original text");
|
|
128
|
+
|
|
129
|
+
const t0 = Date.now();
|
|
130
|
+
const resp = await handleEditIntercept({
|
|
131
|
+
sourceChannel: "slack",
|
|
132
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
133
|
+
externalMessageId: nextEditEventId(),
|
|
134
|
+
sourceMessageId: seeded.channelTs,
|
|
135
|
+
canonicalAssistantId: "self",
|
|
136
|
+
assistantId: "self",
|
|
137
|
+
content: "new text",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(resp.status).toBe(200);
|
|
141
|
+
const respJson = (await resp.json()) as Record<string, unknown>;
|
|
142
|
+
expect(respJson.accepted).toBe(true);
|
|
143
|
+
expect(respJson.duplicate).toBe(false);
|
|
144
|
+
|
|
145
|
+
const after = readMessageRow(seeded.messageId);
|
|
146
|
+
expect(after.content).toBe("new text");
|
|
147
|
+
|
|
148
|
+
expect(after.metadata).not.toBeNull();
|
|
149
|
+
const outer = JSON.parse(after.metadata!);
|
|
150
|
+
expect(outer.userMessageChannel).toBe("slack");
|
|
151
|
+
expect(typeof outer.slackMeta).toBe("string");
|
|
152
|
+
|
|
153
|
+
const slackMeta = readSlackMetadata(outer.slackMeta);
|
|
154
|
+
expect(slackMeta).not.toBeNull();
|
|
155
|
+
expect(slackMeta!.source).toBe("slack");
|
|
156
|
+
expect(slackMeta!.channelId).toBe(seeded.conversationExternalId);
|
|
157
|
+
expect(slackMeta!.channelTs).toBe(seeded.channelTs);
|
|
158
|
+
expect(slackMeta!.eventKind).toBe("message");
|
|
159
|
+
expect(typeof slackMeta!.editedAt).toBe("number");
|
|
160
|
+
expect(slackMeta!.editedAt!).toBeGreaterThanOrEqual(t0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("is idempotent across successive edits", async () => {
|
|
164
|
+
const seeded = await seedSlackMessage({
|
|
165
|
+
conversationExternalId: "C0123CHANNEL",
|
|
166
|
+
channelTs: "1234.5678",
|
|
167
|
+
initialContent: "original text",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
await handleEditIntercept({
|
|
171
|
+
sourceChannel: "slack",
|
|
172
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
173
|
+
externalMessageId: nextEditEventId(),
|
|
174
|
+
sourceMessageId: seeded.channelTs,
|
|
175
|
+
canonicalAssistantId: "self",
|
|
176
|
+
assistantId: "self",
|
|
177
|
+
content: "first edit",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const afterFirst = readMessageRow(seeded.messageId);
|
|
181
|
+
expect(afterFirst.content).toBe("first edit");
|
|
182
|
+
const firstSlackMeta = readSlackMetadata(
|
|
183
|
+
(JSON.parse(afterFirst.metadata!) as Record<string, unknown>)
|
|
184
|
+
.slackMeta as string | null,
|
|
185
|
+
);
|
|
186
|
+
expect(firstSlackMeta).not.toBeNull();
|
|
187
|
+
const firstEditedAt = firstSlackMeta!.editedAt!;
|
|
188
|
+
|
|
189
|
+
// Ensure the second edit's timestamp is observably after the first so the
|
|
190
|
+
// assertion below proves the field was re-stamped, not stale.
|
|
191
|
+
await Bun.sleep(2);
|
|
192
|
+
|
|
193
|
+
await handleEditIntercept({
|
|
194
|
+
sourceChannel: "slack",
|
|
195
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
196
|
+
externalMessageId: nextEditEventId(),
|
|
197
|
+
sourceMessageId: seeded.channelTs,
|
|
198
|
+
canonicalAssistantId: "self",
|
|
199
|
+
assistantId: "self",
|
|
200
|
+
content: "second edit",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const afterSecond = readMessageRow(seeded.messageId);
|
|
204
|
+
expect(afterSecond.content).toBe("second edit");
|
|
205
|
+
const secondSlackMeta = readSlackMetadata(
|
|
206
|
+
(JSON.parse(afterSecond.metadata!) as Record<string, unknown>)
|
|
207
|
+
.slackMeta as string | null,
|
|
208
|
+
);
|
|
209
|
+
expect(secondSlackMeta).not.toBeNull();
|
|
210
|
+
expect(secondSlackMeta!.editedAt!).toBeGreaterThan(firstEditedAt);
|
|
211
|
+
// Other fields stay stable across edits.
|
|
212
|
+
expect(secondSlackMeta!.channelId).toBe(seeded.conversationExternalId);
|
|
213
|
+
expect(secondSlackMeta!.channelTs).toBe(seeded.channelTs);
|
|
214
|
+
expect(secondSlackMeta!.eventKind).toBe("message");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("no-op edit (identical text, e.g. unfurl) skips DB write", async () => {
|
|
218
|
+
const seeded = await seedSlackMessage({
|
|
219
|
+
conversationExternalId: "C0123CHANNEL",
|
|
220
|
+
channelTs: "1234.5678",
|
|
221
|
+
initialContent: "original text",
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const before = readMessageRow(seeded.messageId);
|
|
225
|
+
|
|
226
|
+
const resp = await handleEditIntercept({
|
|
227
|
+
sourceChannel: "slack",
|
|
228
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
229
|
+
externalMessageId: nextEditEventId(),
|
|
230
|
+
sourceMessageId: seeded.channelTs,
|
|
231
|
+
canonicalAssistantId: "self",
|
|
232
|
+
assistantId: "self",
|
|
233
|
+
// Same text as stored -- simulates a Slack unfurl `message_changed`
|
|
234
|
+
// where only attachments changed.
|
|
235
|
+
content: "original text",
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(resp.status).toBe(200);
|
|
239
|
+
const respJson = (await resp.json()) as Record<string, unknown>;
|
|
240
|
+
expect(respJson.accepted).toBe(true);
|
|
241
|
+
expect(respJson.duplicate).toBe(false);
|
|
242
|
+
expect(respJson.noop).toBe(true);
|
|
243
|
+
|
|
244
|
+
const after = readMessageRow(seeded.messageId);
|
|
245
|
+
expect(after.content).toBe(before.content);
|
|
246
|
+
// No metadata mutation either -- the write is fully skipped.
|
|
247
|
+
expect(after.metadata).toBe(before.metadata);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// The lookup retries 5 times with 2s backoff (~10s total) before giving up,
|
|
251
|
+
// so this test legitimately needs to outrun the default 5s per-test timeout.
|
|
252
|
+
test("missing-target edit is a no-op (no throw, no row changed)", async () => {
|
|
253
|
+
const seeded = await seedSlackMessage({
|
|
254
|
+
conversationExternalId: "C0123CHANNEL",
|
|
255
|
+
channelTs: "1234.5678",
|
|
256
|
+
initialContent: "original text",
|
|
257
|
+
});
|
|
258
|
+
const beforeUnknown = readMessageRow(seeded.messageId);
|
|
259
|
+
|
|
260
|
+
const resp = await handleEditIntercept({
|
|
261
|
+
sourceChannel: "slack",
|
|
262
|
+
conversationExternalId: seeded.conversationExternalId,
|
|
263
|
+
// sourceMessageId points at a ts that was never stored.
|
|
264
|
+
externalMessageId: nextEditEventId(),
|
|
265
|
+
sourceMessageId: "9999.0000",
|
|
266
|
+
canonicalAssistantId: "self",
|
|
267
|
+
assistantId: "self",
|
|
268
|
+
content: "new text",
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(resp.status).toBe(200);
|
|
272
|
+
const respJson = (await resp.json()) as Record<string, unknown>;
|
|
273
|
+
expect(respJson.accepted).toBe(true);
|
|
274
|
+
expect(respJson.duplicate).toBe(false);
|
|
275
|
+
|
|
276
|
+
const afterUnknown = readMessageRow(seeded.messageId);
|
|
277
|
+
expect(afterUnknown.content).toBe(beforeUnknown.content);
|
|
278
|
+
expect(afterUnknown.metadata).toBe(beforeUnknown.metadata);
|
|
279
|
+
}, 30_000);
|
|
280
|
+
});
|
|
@@ -83,7 +83,7 @@ describe("ephemeral-permissions", () => {
|
|
|
83
83
|
expect(fileReadRule.createdAt).toBeGreaterThan(0);
|
|
84
84
|
|
|
85
85
|
// Ephemeral task rules should not auto-allow high-risk tools
|
|
86
|
-
|
|
86
|
+
// allowHighRisk is no longer a field on rules
|
|
87
87
|
|
|
88
88
|
// Check other rules have correct tool names
|
|
89
89
|
expect(rules[1].tool).toBe("bash");
|
|
@@ -276,7 +276,7 @@ describe("ephemeral-permissions", () => {
|
|
|
276
276
|
expect(result.decision).toBe("allow");
|
|
277
277
|
});
|
|
278
278
|
|
|
279
|
-
test("high-risk tool still prompts even with ephemeral allow rule
|
|
279
|
+
test("high-risk tool still prompts even with ephemeral allow rule", async () => {
|
|
280
280
|
const ephemeralRules: TrustRule[] = [
|
|
281
281
|
{
|
|
282
282
|
id: "ephemeral:run-1:bash",
|
|
@@ -286,7 +286,7 @@ describe("ephemeral-permissions", () => {
|
|
|
286
286
|
decision: "allow",
|
|
287
287
|
priority: 50,
|
|
288
288
|
createdAt: Date.now(),
|
|
289
|
-
//
|
|
289
|
+
// allowHighRisk is no longer a field
|
|
290
290
|
},
|
|
291
291
|
];
|
|
292
292
|
|
|
@@ -306,6 +306,96 @@ describe("ephemeral-permissions", () => {
|
|
|
306
306
|
});
|
|
307
307
|
});
|
|
308
308
|
|
|
309
|
+
// ── Canonical shape and scope fallback semantics ──────────────────
|
|
310
|
+
//
|
|
311
|
+
// Validates that ephemeral rules follow canonical persisted shapes
|
|
312
|
+
// and that optional scope behaves correctly as a fallback.
|
|
313
|
+
|
|
314
|
+
describe("canonical shape and scope fallback semantics", () => {
|
|
315
|
+
beforeEach(() => {
|
|
316
|
+
clearCache();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("ephemeral rule with scope 'everywhere' matches from any working directory", () => {
|
|
320
|
+
// Use a tool (host_file_read) that has no default allow rule and a
|
|
321
|
+
// high priority so the ephemeral rule wins over the default ask rule.
|
|
322
|
+
const ephemeralRules: TrustRule[] = [
|
|
323
|
+
{
|
|
324
|
+
id: "ephemeral:run-scope:host_file_read",
|
|
325
|
+
tool: "host_file_read",
|
|
326
|
+
pattern: "**",
|
|
327
|
+
scope: "everywhere",
|
|
328
|
+
decision: "allow",
|
|
329
|
+
priority: 2000,
|
|
330
|
+
createdAt: Date.now(),
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
const ctx: PolicyContext = { ephemeralRules };
|
|
335
|
+
|
|
336
|
+
// Should match from /tmp
|
|
337
|
+
const r1 = findHighestPriorityRule(
|
|
338
|
+
"host_file_read",
|
|
339
|
+
["host_file_read:/tmp/foo.txt"],
|
|
340
|
+
"/tmp",
|
|
341
|
+
ctx,
|
|
342
|
+
);
|
|
343
|
+
expect(r1).not.toBeNull();
|
|
344
|
+
expect(r1!.id).toBe("ephemeral:run-scope:host_file_read");
|
|
345
|
+
|
|
346
|
+
// Should match from /home/user/project
|
|
347
|
+
const r2 = findHighestPriorityRule(
|
|
348
|
+
"host_file_read",
|
|
349
|
+
["host_file_read:/home/user/project/bar.ts"],
|
|
350
|
+
"/home/user/project",
|
|
351
|
+
ctx,
|
|
352
|
+
);
|
|
353
|
+
expect(r2).not.toBeNull();
|
|
354
|
+
expect(r2!.id).toBe("ephemeral:run-scope:host_file_read");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("ephemeral rule does not carry stray metadata fields for non-scoped tools", () => {
|
|
358
|
+
// buildTaskRules generates canonical ephemeral rules.
|
|
359
|
+
// For a tool like file_read (scoped family), the generated rule
|
|
360
|
+
// should have no executionTarget.
|
|
361
|
+
const rules = buildTaskRules("run-canon", ["file_read", "bash"], "/tmp");
|
|
362
|
+
|
|
363
|
+
for (const rule of rules) {
|
|
364
|
+
expect(rule.scope).toBe("everywhere");
|
|
365
|
+
expect(rule.executionTarget).toBeUndefined();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("ephemeral rule with project scope does not match sibling project", () => {
|
|
370
|
+
const ephemeralRules: TrustRule[] = [
|
|
371
|
+
{
|
|
372
|
+
id: "ephemeral:run-proj:file_write",
|
|
373
|
+
tool: "file_write",
|
|
374
|
+
pattern: "**",
|
|
375
|
+
scope: "/home/user/project-a",
|
|
376
|
+
decision: "allow",
|
|
377
|
+
priority: 75,
|
|
378
|
+
createdAt: Date.now(),
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
|
|
382
|
+
const ctx: PolicyContext = { ephemeralRules };
|
|
383
|
+
|
|
384
|
+
// Should NOT match from a sibling directory
|
|
385
|
+
const result = findHighestPriorityRule(
|
|
386
|
+
"file_write",
|
|
387
|
+
["file_write:/home/user/project-b/file.ts"],
|
|
388
|
+
"/home/user/project-b",
|
|
389
|
+
ctx,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
// The ephemeral rule should not be the match (scope mismatch)
|
|
393
|
+
if (result) {
|
|
394
|
+
expect(result.id).not.toBe("ephemeral:run-proj:file_write");
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
309
399
|
describe("workspace mode interactions", () => {
|
|
310
400
|
beforeEach(() => {
|
|
311
401
|
clearCache();
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getCalibrationSnapshot,
|
|
5
|
+
getCorrection,
|
|
6
|
+
recordEstimate,
|
|
7
|
+
resetCalibrations,
|
|
8
|
+
} from "../context/estimator-calibration.js";
|
|
9
|
+
import {
|
|
10
|
+
estimatePromptTokens,
|
|
11
|
+
getCalibrationProviderKey,
|
|
12
|
+
} from "../context/token-estimator.js";
|
|
13
|
+
import type { Message, Provider } from "../providers/types.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Integration-style tests that exercise the full self-calibration loop end
|
|
17
|
+
* to end:
|
|
18
|
+
* 1. Estimate is recorded for a `(provider, model)` pair via
|
|
19
|
+
* `handleUsage` (the record side still threads the provider-echoed
|
|
20
|
+
* model through `recordEstimate`).
|
|
21
|
+
* 2. A subsequent `estimatePromptTokens` lookup picks up the learned
|
|
22
|
+
* correction via the per-provider aggregate key `(provider, "")`.
|
|
23
|
+
* Lookup always uses the aggregate — model-specific keys are only
|
|
24
|
+
* read as a fallback inside `getCorrection`.
|
|
25
|
+
*
|
|
26
|
+
* Since the `modelId` lookup option has been removed from the public
|
|
27
|
+
* token-estimator API, the lookup side always converges to the aggregate.
|
|
28
|
+
* `recordEstimate` still updates both the specific `(provider, model)`
|
|
29
|
+
* key AND the `(provider, "")` aggregate on every sample, so the
|
|
30
|
+
* aggregate stays accurate even as per-model data accumulates.
|
|
31
|
+
*/
|
|
32
|
+
describe("estimator calibration — end-to-end recording → lookup", () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
resetCalibrations();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a representative message history with enough content to clear the
|
|
39
|
+
* MIN_SAMPLE_MAGNITUDE floor (500 tokens). Each message repeats a block of
|
|
40
|
+
* text large enough to make the heuristic estimator produce a substantial
|
|
41
|
+
* token count so the calibration machinery actually runs.
|
|
42
|
+
*/
|
|
43
|
+
function largeHistory(): Message[] {
|
|
44
|
+
const body = "lorem ipsum dolor sit amet ".repeat(500);
|
|
45
|
+
return [
|
|
46
|
+
{ role: "user", content: [{ type: "text", text: body }] },
|
|
47
|
+
{ role: "assistant", content: [{ type: "text", text: body }] },
|
|
48
|
+
{ role: "user", content: [{ type: "text", text: body }] },
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test("subsequent estimate picks up the aggregate-key correction", () => {
|
|
53
|
+
const provider: Provider = {
|
|
54
|
+
name: "anthropic",
|
|
55
|
+
async sendMessage() {
|
|
56
|
+
throw new Error("not used in this test");
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const model = "claude-sonnet-4-5";
|
|
60
|
+
const history = largeHistory();
|
|
61
|
+
|
|
62
|
+
// 1. Raw estimate (what agent/loop.ts computes pre-send).
|
|
63
|
+
const preSend = estimatePromptTokens(history, "system", {
|
|
64
|
+
providerName: getCalibrationProviderKey(provider),
|
|
65
|
+
});
|
|
66
|
+
expect(preSend).toBeGreaterThan(0);
|
|
67
|
+
|
|
68
|
+
// Baseline: no correction recorded yet.
|
|
69
|
+
expect(getCorrection("anthropic", "")).toBe(1.0);
|
|
70
|
+
|
|
71
|
+
// 2. Provider returns ground truth (simulating `handleUsage`, which
|
|
72
|
+
// still records under (provider, event.model) and folds into the
|
|
73
|
+
// aggregate). Simulate a systematic 30% underestimate.
|
|
74
|
+
const groundTruth = Math.ceil(preSend * 1.3);
|
|
75
|
+
recordEstimate(
|
|
76
|
+
getCalibrationProviderKey(provider),
|
|
77
|
+
model,
|
|
78
|
+
preSend,
|
|
79
|
+
groundTruth,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// 3. Lookup under the aggregate key now returns the learned ratio.
|
|
83
|
+
expect(getCorrection("anthropic", "")).toBeCloseTo(1.3, 3);
|
|
84
|
+
|
|
85
|
+
// And the corrected estimate moves toward the ground truth.
|
|
86
|
+
const corrected = estimatePromptTokens(history, "system", {
|
|
87
|
+
providerName: getCalibrationProviderKey(provider),
|
|
88
|
+
});
|
|
89
|
+
// With correction factor ≈1.3, corrected estimate is within 1 token of
|
|
90
|
+
// the ground truth (Math.ceil rounding).
|
|
91
|
+
expect(corrected).toBeGreaterThan(preSend);
|
|
92
|
+
expect(Math.abs(corrected - groundTruth)).toBeLessThanOrEqual(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("record with model writes both the specific and aggregate keys", () => {
|
|
96
|
+
// Simulate a preflight site that records against (anthropic, sonnet).
|
|
97
|
+
// `recordEstimate` also folds the sample into the `(anthropic, "")`
|
|
98
|
+
// aggregate so aggregate-key callers see the correction.
|
|
99
|
+
const provider: Provider = {
|
|
100
|
+
name: "anthropic",
|
|
101
|
+
async sendMessage() {
|
|
102
|
+
throw new Error("not used");
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
const history = largeHistory();
|
|
106
|
+
|
|
107
|
+
const preSend = estimatePromptTokens(history, "system", {
|
|
108
|
+
providerName: getCalibrationProviderKey(provider),
|
|
109
|
+
});
|
|
110
|
+
const groundTruth = Math.ceil(preSend * 1.25);
|
|
111
|
+
|
|
112
|
+
recordEstimate(
|
|
113
|
+
getCalibrationProviderKey(provider),
|
|
114
|
+
"claude-sonnet-4-5",
|
|
115
|
+
preSend,
|
|
116
|
+
groundTruth,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// A subsequent lookup via the token-estimator uses the per-provider
|
|
120
|
+
// aggregate (the only key the public API reads).
|
|
121
|
+
const correctedAggregate = estimatePromptTokens(history, "system", {
|
|
122
|
+
providerName: getCalibrationProviderKey(provider),
|
|
123
|
+
});
|
|
124
|
+
// Aggregate ratio ≈ 1.25 (first sample snaps to exact ratio).
|
|
125
|
+
expect(correctedAggregate).toBe(Math.ceil(preSend * 1.25));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("wrapper provider (OpenRouter → Anthropic) uses the canonical key on both sides", () => {
|
|
129
|
+
// This is the Devin scenario: OpenRouter wraps Anthropic. If the record
|
|
130
|
+
// site used `name` ("openrouter") and the lookup site used
|
|
131
|
+
// `tokenEstimationProvider` ("anthropic"), the data would be scattered
|
|
132
|
+
// across mismatched keys and calibration would silently fail.
|
|
133
|
+
// `getCalibrationProviderKey` gives us one source of truth.
|
|
134
|
+
const openrouter: Provider = {
|
|
135
|
+
name: "openrouter",
|
|
136
|
+
tokenEstimationProvider: "anthropic",
|
|
137
|
+
async sendMessage() {
|
|
138
|
+
throw new Error("not used");
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
const model = "anthropic/claude-sonnet-4-5";
|
|
142
|
+
const history = largeHistory();
|
|
143
|
+
|
|
144
|
+
// Pre-send estimate via the canonical key.
|
|
145
|
+
const preSend = estimatePromptTokens(history, "system", {
|
|
146
|
+
providerName: getCalibrationProviderKey(openrouter),
|
|
147
|
+
});
|
|
148
|
+
expect(preSend).toBeGreaterThan(0);
|
|
149
|
+
|
|
150
|
+
// Provider returns ground truth. `handleUsage` uses the same helper
|
|
151
|
+
// to pick the calibration key, so the record and lookup sides agree.
|
|
152
|
+
const groundTruth = Math.ceil(preSend * 1.2);
|
|
153
|
+
recordEstimate(
|
|
154
|
+
getCalibrationProviderKey(openrouter),
|
|
155
|
+
model,
|
|
156
|
+
preSend,
|
|
157
|
+
groundTruth,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Lookup under "anthropic" — the canonical upstream key — returns the
|
|
161
|
+
// ratio. See note above about precision=3.
|
|
162
|
+
expect(getCorrection("anthropic", model)).toBeCloseTo(1.2, 3);
|
|
163
|
+
// Aggregate under the canonical upstream key is also populated.
|
|
164
|
+
expect(getCorrection("anthropic", "")).toBeCloseTo(1.2, 3);
|
|
165
|
+
// And under the bare wrapper name stays at the default, because NOTHING
|
|
166
|
+
// was recorded under "openrouter".
|
|
167
|
+
expect(getCorrection("openrouter", "")).toBe(1.0);
|
|
168
|
+
|
|
169
|
+
// The snapshot reflects a single (provider, model) key + aggregate under
|
|
170
|
+
// the canonical upstream key — never under the wrapper name.
|
|
171
|
+
const keys = getCalibrationSnapshot().map(
|
|
172
|
+
(e) => `${e.provider}::${e.model}`,
|
|
173
|
+
);
|
|
174
|
+
expect(keys).toContain(`anthropic::${model}`);
|
|
175
|
+
expect(keys).toContain("anthropic::");
|
|
176
|
+
expect(keys).not.toContain(`openrouter::${model}`);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a run of consistent samples pulls the estimate toward ground truth", () => {
|
|
180
|
+
// The EWMA should converge quickly. After five consistent 1.3 samples
|
|
181
|
+
// the correction should be within 1% of 1.3, and the corrected estimate
|
|
182
|
+
// should be within 1% of the ground truth.
|
|
183
|
+
const model = "claude-sonnet-4-5";
|
|
184
|
+
const history = largeHistory();
|
|
185
|
+
|
|
186
|
+
const preSend = estimatePromptTokens(history, "system", {
|
|
187
|
+
providerName: "anthropic",
|
|
188
|
+
});
|
|
189
|
+
const groundTruth = Math.ceil(preSend * 1.3);
|
|
190
|
+
|
|
191
|
+
for (let i = 0; i < 5; i++) {
|
|
192
|
+
recordEstimate("anthropic", model, preSend, groundTruth);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const finalCorrection = getCorrection("anthropic", "");
|
|
196
|
+
// EWMA with alpha=0.2 on constant 1.3 stays at 1.3 from the first sample
|
|
197
|
+
// onward (all deltas are 0 after the initial snap). `precision=3` gives
|
|
198
|
+
// us ~0.0005 tolerance which covers the Math.ceil rounding noise.
|
|
199
|
+
expect(finalCorrection).toBeCloseTo(1.3, 3);
|
|
200
|
+
|
|
201
|
+
const corrected = estimatePromptTokens(history, "system", {
|
|
202
|
+
providerName: "anthropic",
|
|
203
|
+
});
|
|
204
|
+
// Corrected should be very close to the ground truth (within 1 token
|
|
205
|
+
// because of the Math.ceil rounding at the end of estimatePromptTokens).
|
|
206
|
+
expect(Math.abs(corrected - groundTruth)).toBeLessThanOrEqual(1);
|
|
207
|
+
});
|
|
208
|
+
});
|