@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
|
@@ -32,6 +32,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
34
|
import { buildCliProgram } from "../cli/program.js";
|
|
35
|
+
import type { RiskClassification } from "../permissions/checker.js";
|
|
35
36
|
import { classifyRisk } from "../permissions/checker.js";
|
|
36
37
|
import { RiskLevel } from "../permissions/types.js";
|
|
37
38
|
|
|
@@ -39,17 +40,17 @@ import { RiskLevel } from "../permissions/types.js";
|
|
|
39
40
|
* Assert that a command classifies as Low risk, with a descriptive failure
|
|
40
41
|
* message that guides developers toward the correct fix.
|
|
41
42
|
*/
|
|
42
|
-
function expectLowRisk(command: string, actual:
|
|
43
|
-
if (actual !== RiskLevel.Low) {
|
|
43
|
+
function expectLowRisk(command: string, actual: RiskClassification): void {
|
|
44
|
+
if (actual.level !== RiskLevel.Low) {
|
|
44
45
|
throw new Error(
|
|
45
|
-
`"${command}" classified as ${actual} instead of Low. ` +
|
|
46
|
+
`"${command}" classified as ${actual.level} instead of Low. ` +
|
|
46
47
|
`assistant CLI commands must be Low risk by default — the assistant ` +
|
|
47
48
|
`uses its own CLI during normal operation. If you need risk ` +
|
|
48
49
|
`escalation for specific subcommands, add them to the elevated ` +
|
|
49
50
|
`risk tests in this guard test with justification.`,
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
|
-
expect(actual).toBe(RiskLevel.Low);
|
|
53
|
+
expect(actual.level).toBe(RiskLevel.Low);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// Dynamically extract subcommand names from the CLI program definition.
|
|
@@ -84,11 +85,7 @@ describe("CLI command risk guard: assistant commands", () => {
|
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
test("assistant with flags classifies as Low risk", async () => {
|
|
87
|
-
const flagCommands = [
|
|
88
|
-
"assistant --version",
|
|
89
|
-
"assistant --help",
|
|
90
|
-
"assistant doctor --verbose",
|
|
91
|
-
];
|
|
88
|
+
const flagCommands = ["assistant --version", "assistant --help"];
|
|
92
89
|
|
|
93
90
|
for (const command of flagCommands) {
|
|
94
91
|
const risk = await classifyRisk("bash", { command });
|
|
@@ -105,56 +102,56 @@ describe("CLI command risk guard: elevated assistant subcommands", () => {
|
|
|
105
102
|
const risk = await classifyRisk("bash", {
|
|
106
103
|
command: "assistant oauth token",
|
|
107
104
|
});
|
|
108
|
-
expect(risk).toBe(RiskLevel.High);
|
|
105
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
109
106
|
});
|
|
110
107
|
|
|
111
108
|
test("assistant oauth mode --set is High risk (changes auth mode)", async () => {
|
|
112
109
|
const risk = await classifyRisk("bash", {
|
|
113
110
|
command: "assistant oauth mode --set managed",
|
|
114
111
|
});
|
|
115
|
-
expect(risk).toBe(RiskLevel.High);
|
|
112
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
116
113
|
});
|
|
117
114
|
|
|
118
115
|
test("assistant oauth mode --set=value is High risk (equals syntax)", async () => {
|
|
119
116
|
const risk = await classifyRisk("bash", {
|
|
120
117
|
command: "assistant oauth mode google --set=managed",
|
|
121
118
|
});
|
|
122
|
-
expect(risk).toBe(RiskLevel.High);
|
|
119
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
123
120
|
});
|
|
124
121
|
|
|
125
122
|
test("assistant oauth mode without --set is Low risk (read-only)", async () => {
|
|
126
123
|
const risk = await classifyRisk("bash", {
|
|
127
124
|
command: "assistant oauth mode",
|
|
128
125
|
});
|
|
129
|
-
expect(risk).toBe(RiskLevel.Low);
|
|
126
|
+
expect(risk.level).toBe(RiskLevel.Low);
|
|
130
127
|
});
|
|
131
128
|
|
|
132
129
|
test("assistant credentials reveal is High risk (exposes secrets)", async () => {
|
|
133
130
|
const risk = await classifyRisk("bash", {
|
|
134
131
|
command: "assistant credentials reveal",
|
|
135
132
|
});
|
|
136
|
-
expect(risk).toBe(RiskLevel.High);
|
|
133
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
137
134
|
});
|
|
138
135
|
|
|
139
136
|
test("assistant oauth request is Medium risk (initiates OAuth flow)", async () => {
|
|
140
137
|
const risk = await classifyRisk("bash", {
|
|
141
138
|
command: "assistant oauth request",
|
|
142
139
|
});
|
|
143
|
-
expect(risk).toBe(RiskLevel.Medium);
|
|
140
|
+
expect(risk.level).toBe(RiskLevel.Medium);
|
|
144
141
|
});
|
|
145
142
|
|
|
146
143
|
test("assistant oauth connect is Medium risk (modifies OAuth connections)", async () => {
|
|
147
144
|
const risk = await classifyRisk("bash", {
|
|
148
145
|
command: "assistant oauth connect",
|
|
149
146
|
});
|
|
150
|
-
expect(risk).toBe(RiskLevel.Medium);
|
|
147
|
+
expect(risk.level).toBe(RiskLevel.Medium);
|
|
151
148
|
});
|
|
152
149
|
|
|
153
150
|
test("assistant oauth disconnect is Medium risk (removes OAuth connections)", async () => {
|
|
154
151
|
const risk = await classifyRisk("bash", {
|
|
155
152
|
command: "assistant oauth disconnect",
|
|
156
153
|
});
|
|
157
|
-
expect(risk).toBe(RiskLevel.Medium);
|
|
154
|
+
expect(risk.level).toBe(RiskLevel.Medium);
|
|
158
155
|
});
|
|
159
156
|
|
|
160
157
|
test("--help on non-elevated subcommands remains Low risk", async () => {
|
|
@@ -199,12 +196,12 @@ describe("CLI command risk guard: elevated assistant subcommands", () => {
|
|
|
199
196
|
// THEN --help does not bypass the elevated risk level
|
|
200
197
|
for (const command of highRiskWithHelp) {
|
|
201
198
|
const risk = await classifyRisk("bash", { command });
|
|
202
|
-
expect(risk).toBe(RiskLevel.High);
|
|
199
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
203
200
|
}
|
|
204
201
|
|
|
205
202
|
for (const command of mediumRiskWithHelp) {
|
|
206
203
|
const risk = await classifyRisk("bash", { command });
|
|
207
|
-
expect(risk).toBe(RiskLevel.Medium);
|
|
204
|
+
expect(risk.level).toBe(RiskLevel.Medium);
|
|
208
205
|
}
|
|
209
206
|
});
|
|
210
207
|
|
|
@@ -212,14 +209,14 @@ describe("CLI command risk guard: elevated assistant subcommands", () => {
|
|
|
212
209
|
const risk = await classifyRisk("bash", {
|
|
213
210
|
command: "assistant credentials reveal 123 --service --help",
|
|
214
211
|
});
|
|
215
|
-
expect(risk).toBe(RiskLevel.High);
|
|
212
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
216
213
|
});
|
|
217
214
|
|
|
218
215
|
test("-h used as option value does not downgrade oauth mode --set risk", async () => {
|
|
219
216
|
const risk = await classifyRisk("bash", {
|
|
220
217
|
command: "assistant oauth mode --set -h",
|
|
221
218
|
});
|
|
222
|
-
expect(risk).toBe(RiskLevel.High);
|
|
219
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
223
220
|
});
|
|
224
221
|
|
|
225
222
|
test("non-sensitive oauth subcommands remain Low risk", async () => {
|
|
@@ -252,28 +249,28 @@ describe("CLI command risk guard: elevated assistant subcommands", () => {
|
|
|
252
249
|
const risk = await classifyRisk("bash", {
|
|
253
250
|
command: "assistant credentials set",
|
|
254
251
|
});
|
|
255
|
-
expect(risk).toBe(RiskLevel.High);
|
|
252
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
256
253
|
});
|
|
257
254
|
|
|
258
255
|
test("assistant credentials delete is High risk (removes stored credentials)", async () => {
|
|
259
256
|
const risk = await classifyRisk("bash", {
|
|
260
257
|
command: "assistant credentials delete",
|
|
261
258
|
});
|
|
262
|
-
expect(risk).toBe(RiskLevel.High);
|
|
259
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
263
260
|
});
|
|
264
261
|
|
|
265
262
|
test("assistant keys set is High risk (modifies API keys)", async () => {
|
|
266
263
|
const risk = await classifyRisk("bash", {
|
|
267
264
|
command: "assistant keys set anthropic sk-ant-xxx",
|
|
268
265
|
});
|
|
269
|
-
expect(risk).toBe(RiskLevel.High);
|
|
266
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
270
267
|
});
|
|
271
268
|
|
|
272
269
|
test("assistant keys delete is High risk (removes API keys)", async () => {
|
|
273
270
|
const risk = await classifyRisk("bash", {
|
|
274
271
|
command: "assistant keys delete openai",
|
|
275
272
|
});
|
|
276
|
-
expect(risk).toBe(RiskLevel.High);
|
|
273
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
277
274
|
});
|
|
278
275
|
|
|
279
276
|
test("non-sensitive keys subcommands remain Low risk", async () => {
|
|
@@ -289,14 +286,14 @@ describe("CLI command risk guard: elevated assistant subcommands", () => {
|
|
|
289
286
|
const risk = await classifyRisk("bash", {
|
|
290
287
|
command: "assistant trust remove abc123",
|
|
291
288
|
});
|
|
292
|
-
expect(risk).toBe(RiskLevel.High);
|
|
289
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
293
290
|
});
|
|
294
291
|
|
|
295
292
|
test("assistant trust clear is High risk (clears all trust rules)", async () => {
|
|
296
293
|
const risk = await classifyRisk("bash", {
|
|
297
294
|
command: "assistant trust clear",
|
|
298
295
|
});
|
|
299
|
-
expect(risk).toBe(RiskLevel.High);
|
|
296
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
300
297
|
});
|
|
301
298
|
|
|
302
299
|
test("non-sensitive trust subcommands remain Low risk", async () => {
|
|
@@ -314,35 +311,35 @@ describe("CLI command risk guard: wrapper program propagation", () => {
|
|
|
314
311
|
const risk = await classifyRisk("bash", {
|
|
315
312
|
command: "env assistant oauth token",
|
|
316
313
|
});
|
|
317
|
-
expect(risk).toBe(RiskLevel.High);
|
|
314
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
318
315
|
});
|
|
319
316
|
|
|
320
317
|
test("nice assistant credentials reveal is High risk", async () => {
|
|
321
318
|
const risk = await classifyRisk("bash", {
|
|
322
319
|
command: "nice assistant credentials reveal",
|
|
323
320
|
});
|
|
324
|
-
expect(risk).toBe(RiskLevel.High);
|
|
321
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
325
322
|
});
|
|
326
323
|
|
|
327
324
|
test("timeout 30 assistant oauth request is Medium risk", async () => {
|
|
328
325
|
const risk = await classifyRisk("bash", {
|
|
329
326
|
command: "timeout 30 assistant oauth request",
|
|
330
327
|
});
|
|
331
|
-
expect(risk).toBe(RiskLevel.Medium);
|
|
328
|
+
expect(risk.level).toBe(RiskLevel.Medium);
|
|
332
329
|
});
|
|
333
330
|
|
|
334
331
|
test("timeout 30 assistant oauth token is High risk", async () => {
|
|
335
332
|
const risk = await classifyRisk("bash", {
|
|
336
333
|
command: "timeout 30 assistant oauth token",
|
|
337
334
|
});
|
|
338
|
-
expect(risk).toBe(RiskLevel.High);
|
|
335
|
+
expect(risk.level).toBe(RiskLevel.High);
|
|
339
336
|
});
|
|
340
337
|
|
|
341
338
|
test("timeout 30 git push is Medium risk", async () => {
|
|
342
339
|
const risk = await classifyRisk("bash", {
|
|
343
340
|
command: "timeout 30 git push",
|
|
344
341
|
});
|
|
345
|
-
expect(risk).toBe(RiskLevel.Medium);
|
|
342
|
+
expect(risk.level).toBe(RiskLevel.Medium);
|
|
346
343
|
});
|
|
347
344
|
|
|
348
345
|
test("timeout 30 git status is Low risk", async () => {
|
|
@@ -361,7 +358,7 @@ describe("CLI command risk guard: wrapper program propagation", () => {
|
|
|
361
358
|
|
|
362
359
|
test("env git push is Medium risk (not Low)", async () => {
|
|
363
360
|
const risk = await classifyRisk("bash", { command: "env git push" });
|
|
364
|
-
expect(risk).toBe(RiskLevel.Medium);
|
|
361
|
+
expect(risk.level).toBe(RiskLevel.Medium);
|
|
365
362
|
});
|
|
366
363
|
|
|
367
364
|
test("env git status is Low risk", async () => {
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit-breaker tests for the compaction path.
|
|
3
|
+
*
|
|
4
|
+
* These exercise the tiny helpers (`isCompactionCircuitOpen`,
|
|
5
|
+
* `trackCompactionOutcome`) that `conversation-agent-loop.ts` uses at every
|
|
6
|
+
* `maybeCompact()` call site. Covering the helpers — rather than wiring up a
|
|
7
|
+
* full `Conversation` — keeps the test fast and isolates the breaker logic
|
|
8
|
+
* from the rest of the loop, which is where bugs actually hide.
|
|
9
|
+
*
|
|
10
|
+
* Acceptance criteria:
|
|
11
|
+
* (a) counter increments on `summaryFailed`
|
|
12
|
+
* (b) circuit opens after exactly 3 failures
|
|
13
|
+
* (c) successful compaction resets counter and circuit
|
|
14
|
+
* (d) open circuit skips auto-compaction but admits `force: true`
|
|
15
|
+
* (e) circuit re-opens after cooldown expiry when 3 more failures accumulate
|
|
16
|
+
* (f) call sites guard `undefined summaryFailed` so early returns do not
|
|
17
|
+
* reset the counter
|
|
18
|
+
* (g) forceCompact-style tracking: resets counter on success, increments on
|
|
19
|
+
* failure, preserves state on early returns
|
|
20
|
+
*/
|
|
21
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
isCompactionCircuitOpen,
|
|
25
|
+
trackCompactionOutcome,
|
|
26
|
+
} from "../daemon/conversation-agent-loop.js";
|
|
27
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
28
|
+
|
|
29
|
+
interface BreakerState {
|
|
30
|
+
consecutiveCompactionFailures: number;
|
|
31
|
+
compactionCircuitOpenUntil: number | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeState(): BreakerState {
|
|
35
|
+
return {
|
|
36
|
+
consecutiveCompactionFailures: 0,
|
|
37
|
+
compactionCircuitOpenUntil: null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function collectEvents(): {
|
|
42
|
+
events: ServerMessage[];
|
|
43
|
+
onEvent: (msg: ServerMessage) => void;
|
|
44
|
+
} {
|
|
45
|
+
const events: ServerMessage[] = [];
|
|
46
|
+
return { events, onEvent: (msg) => events.push(msg) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe("compaction circuit breaker", () => {
|
|
50
|
+
let originalDateNow: () => number;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
originalDateNow = Date.now;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
Date.now = originalDateNow;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("(a) counter increments on each summaryFailed outcome", () => {
|
|
61
|
+
const state = makeState();
|
|
62
|
+
const { onEvent, events } = collectEvents();
|
|
63
|
+
|
|
64
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
65
|
+
expect(state.consecutiveCompactionFailures).toBe(1);
|
|
66
|
+
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
67
|
+
expect(events).toHaveLength(0);
|
|
68
|
+
|
|
69
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
70
|
+
expect(state.consecutiveCompactionFailures).toBe(2);
|
|
71
|
+
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
72
|
+
expect(events).toHaveLength(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("(b) circuit opens after exactly 3 consecutive failures", () => {
|
|
76
|
+
const fixedNow = 1_700_000_000_000;
|
|
77
|
+
Date.now = () => fixedNow;
|
|
78
|
+
|
|
79
|
+
const state = makeState();
|
|
80
|
+
const { onEvent, events } = collectEvents();
|
|
81
|
+
|
|
82
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
83
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
84
|
+
// Two failures — circuit still closed.
|
|
85
|
+
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
86
|
+
expect(events).toHaveLength(0);
|
|
87
|
+
|
|
88
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
89
|
+
// Third failure — circuit trips and fires the event exactly once.
|
|
90
|
+
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
91
|
+
expect(state.compactionCircuitOpenUntil).toBe(fixedNow + 60 * 60 * 1000);
|
|
92
|
+
expect(events).toHaveLength(1);
|
|
93
|
+
expect(events[0]).toEqual({
|
|
94
|
+
type: "compaction_circuit_open",
|
|
95
|
+
reason: "3_consecutive_failures",
|
|
96
|
+
openUntil: fixedNow + 60 * 60 * 1000,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Further failures do not re-fire the event while the circuit is open.
|
|
100
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
101
|
+
expect(state.consecutiveCompactionFailures).toBe(4);
|
|
102
|
+
expect(events).toHaveLength(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("(c) successful compaction resets counter and clears circuit", () => {
|
|
106
|
+
const fixedNow = 1_700_000_000_000;
|
|
107
|
+
Date.now = () => fixedNow;
|
|
108
|
+
|
|
109
|
+
const state = makeState();
|
|
110
|
+
const { onEvent } = collectEvents();
|
|
111
|
+
|
|
112
|
+
// Trip the breaker.
|
|
113
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
114
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
115
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
116
|
+
expect(state.compactionCircuitOpenUntil).not.toBeNull();
|
|
117
|
+
|
|
118
|
+
// Success resets state.
|
|
119
|
+
trackCompactionOutcome(state, false, onEvent);
|
|
120
|
+
expect(state.consecutiveCompactionFailures).toBe(0);
|
|
121
|
+
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
122
|
+
|
|
123
|
+
// `summaryFailed` undefined (never attempted the LLM call) currently
|
|
124
|
+
// takes the "not failed" branch, which is why callers must guard the
|
|
125
|
+
// helper with `summaryFailed !== undefined` — otherwise an early-return
|
|
126
|
+
// `maybeCompact()` would silently reset the counter. The regression test
|
|
127
|
+
// below documents that invariant from the caller's perspective.
|
|
128
|
+
trackCompactionOutcome(state, undefined, onEvent);
|
|
129
|
+
expect(state.consecutiveCompactionFailures).toBe(0);
|
|
130
|
+
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("(d) isCompactionCircuitOpen reflects state and expiry", () => {
|
|
134
|
+
const fixedNow = 1_700_000_000_000;
|
|
135
|
+
Date.now = () => fixedNow;
|
|
136
|
+
|
|
137
|
+
const state = makeState();
|
|
138
|
+
expect(isCompactionCircuitOpen(state)).toBe(false);
|
|
139
|
+
|
|
140
|
+
// Trip the breaker — now open.
|
|
141
|
+
const { onEvent } = collectEvents();
|
|
142
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
143
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
144
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
145
|
+
expect(isCompactionCircuitOpen(state)).toBe(true);
|
|
146
|
+
|
|
147
|
+
// After cooldown expires the helper reports closed again, even without an
|
|
148
|
+
// explicit reset — the open-until timestamp is the only source of truth
|
|
149
|
+
// for the gate.
|
|
150
|
+
Date.now = () => fixedNow + 60 * 60 * 1000 + 1;
|
|
151
|
+
expect(isCompactionCircuitOpen(state)).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("(d) open circuit skips auto-compaction but admits force:true", () => {
|
|
155
|
+
// Simulate the decision the agent-loop site makes with a counter that
|
|
156
|
+
// only increments when compaction actually runs.
|
|
157
|
+
const fixedNow = 1_700_000_000_000;
|
|
158
|
+
Date.now = () => fixedNow;
|
|
159
|
+
|
|
160
|
+
const state = makeState();
|
|
161
|
+
const { onEvent } = collectEvents();
|
|
162
|
+
|
|
163
|
+
let compactionCalls = 0;
|
|
164
|
+
const runCompactionIfAllowed = (opts: { force?: boolean }) => {
|
|
165
|
+
// Mirror conversation-agent-loop.ts site 1:
|
|
166
|
+
// auto paths gate on !isCompactionCircuitOpen(ctx);
|
|
167
|
+
// force paths bypass the gate.
|
|
168
|
+
if (!opts.force && isCompactionCircuitOpen(state)) {
|
|
169
|
+
return { ran: false };
|
|
170
|
+
}
|
|
171
|
+
compactionCalls += 1;
|
|
172
|
+
return { ran: true };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Trip the breaker.
|
|
176
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
177
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
178
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
179
|
+
expect(isCompactionCircuitOpen(state)).toBe(true);
|
|
180
|
+
|
|
181
|
+
// Auto-path is skipped while the circuit is open.
|
|
182
|
+
const autoAttempt = runCompactionIfAllowed({});
|
|
183
|
+
expect(autoAttempt.ran).toBe(false);
|
|
184
|
+
expect(compactionCalls).toBe(0);
|
|
185
|
+
|
|
186
|
+
// Force-path always runs, even with the breaker open.
|
|
187
|
+
const forceAttempt = runCompactionIfAllowed({ force: true });
|
|
188
|
+
expect(forceAttempt.ran).toBe(true);
|
|
189
|
+
expect(compactionCalls).toBe(1);
|
|
190
|
+
|
|
191
|
+
// After a forced compaction succeeds, the counter resets and the circuit
|
|
192
|
+
// closes, unblocking future auto attempts.
|
|
193
|
+
trackCompactionOutcome(state, false, onEvent);
|
|
194
|
+
expect(isCompactionCircuitOpen(state)).toBe(false);
|
|
195
|
+
expect(state.consecutiveCompactionFailures).toBe(0);
|
|
196
|
+
|
|
197
|
+
const autoRetry = runCompactionIfAllowed({});
|
|
198
|
+
expect(autoRetry.ran).toBe(true);
|
|
199
|
+
expect(compactionCalls).toBe(2);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("(e) circuit re-opens after cooldown expiry when 3 more failures accumulate", () => {
|
|
203
|
+
// Regression: before the fix, `trackCompactionOutcome` required
|
|
204
|
+
// `compactionCircuitOpenUntil === null` to open the circuit. Once a
|
|
205
|
+
// cooldown expired, `isCompactionCircuitOpen()` correctly reported
|
|
206
|
+
// "closed" but the stale past-timestamp stayed on the state, so the
|
|
207
|
+
// next 3-strike window could never trip a new cooldown. The fix
|
|
208
|
+
// treats any expired timestamp the same as null.
|
|
209
|
+
const t0 = 1_700_000_000_000;
|
|
210
|
+
Date.now = () => t0;
|
|
211
|
+
|
|
212
|
+
const state = makeState();
|
|
213
|
+
const { onEvent, events } = collectEvents();
|
|
214
|
+
|
|
215
|
+
// Trip the breaker the first time.
|
|
216
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
217
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
218
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
219
|
+
expect(state.compactionCircuitOpenUntil).toBe(t0 + 60 * 60 * 1000);
|
|
220
|
+
expect(events).toHaveLength(1);
|
|
221
|
+
|
|
222
|
+
// Advance past the cooldown window. Manually reset the counter — in
|
|
223
|
+
// production this happens when a subsequent `maybeCompact()` call
|
|
224
|
+
// succeeds (`summaryFailed: false`) after the cooldown elapses, but
|
|
225
|
+
// the bug manifests even when the counter is reset: the stale
|
|
226
|
+
// `compactionCircuitOpenUntil` is what breaks re-opening.
|
|
227
|
+
const t1 = t0 + 60 * 60 * 1000 + 1;
|
|
228
|
+
Date.now = () => t1;
|
|
229
|
+
expect(isCompactionCircuitOpen(state)).toBe(false);
|
|
230
|
+
state.consecutiveCompactionFailures = 0;
|
|
231
|
+
// `compactionCircuitOpenUntil` is deliberately left as the old
|
|
232
|
+
// timestamp to reproduce the bug condition — in practice the null
|
|
233
|
+
// reset only happens on `summaryFailed: false`.
|
|
234
|
+
expect(state.compactionCircuitOpenUntil).toBe(t0 + 60 * 60 * 1000);
|
|
235
|
+
|
|
236
|
+
// Three more failures must trip a fresh cooldown even though the
|
|
237
|
+
// old timestamp is still set.
|
|
238
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
239
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
240
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
241
|
+
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
242
|
+
expect(state.compactionCircuitOpenUntil).toBe(t1 + 60 * 60 * 1000);
|
|
243
|
+
expect(events).toHaveLength(2);
|
|
244
|
+
expect(events[1]).toEqual({
|
|
245
|
+
type: "compaction_circuit_open",
|
|
246
|
+
reason: "3_consecutive_failures",
|
|
247
|
+
openUntil: t1 + 60 * 60 * 1000,
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("(f) call sites guard undefined summaryFailed so early returns don't reset the counter", () => {
|
|
252
|
+
// Regression: `maybeCompact()` returns `summaryFailed: undefined` on
|
|
253
|
+
// early-return paths (no eligible messages, below threshold, cooldown
|
|
254
|
+
// active, truncation-only). Before the fix, the agent loop called
|
|
255
|
+
// `trackCompactionOutcome(ctx, compacted.summaryFailed, onEvent)`
|
|
256
|
+
// unconditionally — `undefined` took the else branch and silently
|
|
257
|
+
// reset the 3-strike counter. Callers must now guard with
|
|
258
|
+
// `summaryFailed !== undefined` at every call site.
|
|
259
|
+
const state = makeState();
|
|
260
|
+
const { onEvent } = collectEvents();
|
|
261
|
+
|
|
262
|
+
// Accumulate two failures, close to tripping the breaker.
|
|
263
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
264
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
265
|
+
expect(state.consecutiveCompactionFailures).toBe(2);
|
|
266
|
+
|
|
267
|
+
// Simulate an early-return result from maybeCompact() (e.g. below
|
|
268
|
+
// threshold) — callers must skip the tracking call entirely.
|
|
269
|
+
const earlyReturn = {
|
|
270
|
+
compacted: false,
|
|
271
|
+
summaryFailed: undefined as boolean | undefined,
|
|
272
|
+
};
|
|
273
|
+
if (earlyReturn.summaryFailed !== undefined) {
|
|
274
|
+
trackCompactionOutcome(state, earlyReturn.summaryFailed, onEvent);
|
|
275
|
+
}
|
|
276
|
+
// Counter preserved — the early return did not reset progress toward
|
|
277
|
+
// tripping the breaker.
|
|
278
|
+
expect(state.consecutiveCompactionFailures).toBe(2);
|
|
279
|
+
|
|
280
|
+
// A third real failure then trips the breaker as expected.
|
|
281
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
282
|
+
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
283
|
+
expect(state.compactionCircuitOpenUntil).not.toBeNull();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("(g) forceCompact-style tracking resets counter on success, increments on failure", () => {
|
|
287
|
+
// Regression: `Conversation.forceCompact()` previously didn't track
|
|
288
|
+
// circuit-breaker outcomes. A successful user `/compact` wouldn't clear
|
|
289
|
+
// an accumulating counter and a failed forced compaction wouldn't
|
|
290
|
+
// contribute to tripping the breaker. The fix calls
|
|
291
|
+
// `trackCompactionOutcome(this, result.summaryFailed, this.sendToClient)`
|
|
292
|
+
// after `maybeCompact` — guarded by `summaryFailed !== undefined` so
|
|
293
|
+
// early-return paths don't reset the counter.
|
|
294
|
+
const state = makeState();
|
|
295
|
+
const { onEvent } = collectEvents();
|
|
296
|
+
|
|
297
|
+
// Simulate forceCompact: call maybeCompact with force:true, then
|
|
298
|
+
// track the outcome the same way forceCompact now does.
|
|
299
|
+
const trackForceCompact = (result: {
|
|
300
|
+
summaryFailed?: boolean;
|
|
301
|
+
compacted: boolean;
|
|
302
|
+
}): void => {
|
|
303
|
+
if (result.summaryFailed !== undefined) {
|
|
304
|
+
trackCompactionOutcome(state, result.summaryFailed, onEvent);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Two failures via the auto path …
|
|
309
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
310
|
+
trackCompactionOutcome(state, true, onEvent);
|
|
311
|
+
expect(state.consecutiveCompactionFailures).toBe(2);
|
|
312
|
+
|
|
313
|
+
// … then the user hits /compact and the forced call succeeds. This
|
|
314
|
+
// must clear the stuck counter so the conversation isn't one
|
|
315
|
+
// auto-failure away from a cooldown.
|
|
316
|
+
trackForceCompact({ summaryFailed: false, compacted: true });
|
|
317
|
+
expect(state.consecutiveCompactionFailures).toBe(0);
|
|
318
|
+
expect(state.compactionCircuitOpenUntil).toBeNull();
|
|
319
|
+
|
|
320
|
+
// Conversely, three forced failures must trip the breaker too — a
|
|
321
|
+
// run of broken summaries is a provider-health signal regardless of
|
|
322
|
+
// whether the caller bypassed the breaker.
|
|
323
|
+
trackForceCompact({ summaryFailed: true, compacted: true });
|
|
324
|
+
trackForceCompact({ summaryFailed: true, compacted: true });
|
|
325
|
+
trackForceCompact({ summaryFailed: true, compacted: true });
|
|
326
|
+
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
327
|
+
expect(state.compactionCircuitOpenUntil).not.toBeNull();
|
|
328
|
+
|
|
329
|
+
// An early-return forceCompact (e.g. no eligible messages) must not
|
|
330
|
+
// reset the counter — the breaker should stay open.
|
|
331
|
+
const wasOpenUntil = state.compactionCircuitOpenUntil;
|
|
332
|
+
trackForceCompact({ summaryFailed: undefined, compacted: false });
|
|
333
|
+
expect(state.consecutiveCompactionFailures).toBe(3);
|
|
334
|
+
expect(state.compactionCircuitOpenUntil).toBe(wasOpenUntil);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -10,30 +10,27 @@ describe("AnalysisConfigSchema", () => {
|
|
|
10
10
|
const parsed = AnalysisConfigSchema.parse({});
|
|
11
11
|
expect(parsed.batchSize).toBe(30);
|
|
12
12
|
expect(parsed.idleTimeoutMs).toBe(600_000);
|
|
13
|
-
expect(parsed.modelIntent).toBeUndefined();
|
|
14
|
-
expect(parsed.modelOverride).toBeUndefined();
|
|
15
13
|
});
|
|
16
14
|
|
|
17
|
-
test("custom values round-trip", () => {
|
|
15
|
+
test("custom batch/idle values round-trip", () => {
|
|
18
16
|
const input = {
|
|
19
17
|
batchSize: 50,
|
|
20
18
|
idleTimeoutMs: 120_000,
|
|
21
|
-
modelIntent: "quality-optimized" as const,
|
|
22
|
-
modelOverride: "anthropic/claude-opus-4-6",
|
|
23
19
|
};
|
|
24
20
|
const parsed = AnalysisConfigSchema.parse(input);
|
|
25
21
|
expect(parsed).toEqual(input);
|
|
26
22
|
});
|
|
27
23
|
|
|
28
|
-
test("
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
test("legacy modelIntent/modelOverride are stripped after PR 19 cleanup", () => {
|
|
25
|
+
// Both fields moved to llm.callSites.analyzeConversation in PR 4 and
|
|
26
|
+
// were removed from the schema in PR 19. Zod silently strips unknown
|
|
27
|
+
// keys; migration 039 erases them from disk.
|
|
28
|
+
const parsed = AnalysisConfigSchema.parse({
|
|
29
|
+
modelIntent: "quality-optimized",
|
|
30
|
+
modelOverride: "anthropic/claude-opus-4-6",
|
|
31
|
+
});
|
|
32
|
+
expect((parsed as Record<string, unknown>).modelIntent).toBeUndefined();
|
|
33
|
+
expect((parsed as Record<string, unknown>).modelOverride).toBeUndefined();
|
|
37
34
|
});
|
|
38
35
|
|
|
39
36
|
test("rejects batchSize: 0 (must be positive)", () => {
|
|
@@ -60,18 +57,6 @@ describe("AnalysisConfigSchema", () => {
|
|
|
60
57
|
const result = AnalysisConfigSchema.safeParse({ idleTimeoutMs: -1000 });
|
|
61
58
|
expect(result.success).toBe(false);
|
|
62
59
|
});
|
|
63
|
-
|
|
64
|
-
test("rejects invalid modelIntent value", () => {
|
|
65
|
-
const result = AnalysisConfigSchema.safeParse({
|
|
66
|
-
modelIntent: "bogus-intent",
|
|
67
|
-
});
|
|
68
|
-
expect(result.success).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("rejects non-string modelOverride", () => {
|
|
72
|
-
const result = AnalysisConfigSchema.safeParse({ modelOverride: 42 });
|
|
73
|
-
expect(result.success).toBe(false);
|
|
74
|
-
});
|
|
75
60
|
});
|
|
76
61
|
|
|
77
62
|
describe("AssistantConfigSchema — analysis integration", () => {
|
|
@@ -88,13 +73,11 @@ describe("AssistantConfigSchema — analysis integration", () => {
|
|
|
88
73
|
analysis: {
|
|
89
74
|
batchSize: 15,
|
|
90
75
|
idleTimeoutMs: 300_000,
|
|
91
|
-
modelIntent: "latency-optimized",
|
|
92
76
|
},
|
|
93
77
|
});
|
|
94
78
|
expect(parsed.analysis).toEqual({
|
|
95
79
|
batchSize: 15,
|
|
96
80
|
idleTimeoutMs: 300_000,
|
|
97
|
-
modelIntent: "latency-optimized",
|
|
98
81
|
});
|
|
99
82
|
});
|
|
100
83
|
});
|