@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
|
@@ -212,19 +212,7 @@ describe("isWorkspaceScopedInvocation", () => {
|
|
|
212
212
|
// ── Network tools ──────────────────────────────────────────────────
|
|
213
213
|
|
|
214
214
|
describe("network tools", () => {
|
|
215
|
-
const networkTools = [
|
|
216
|
-
"web_search",
|
|
217
|
-
"web_fetch",
|
|
218
|
-
"browser_navigate",
|
|
219
|
-
"browser_click",
|
|
220
|
-
"browser_type",
|
|
221
|
-
"browser_scroll",
|
|
222
|
-
"browser_screenshot",
|
|
223
|
-
"browser_close",
|
|
224
|
-
"browser_attach",
|
|
225
|
-
"browser_detach",
|
|
226
|
-
"network_request",
|
|
227
|
-
];
|
|
215
|
+
const networkTools = ["web_search", "web_fetch", "network_request"];
|
|
228
216
|
|
|
229
217
|
for (const tool of networkTools) {
|
|
230
218
|
test(`${tool} is NOT workspace-scoped`, () => {
|
|
@@ -412,12 +412,11 @@ function mapDecisionToOptionId(
|
|
|
412
412
|
decision === "allow_10m" ||
|
|
413
413
|
decision === "allow_conversation" ||
|
|
414
414
|
decision === "always_allow" ||
|
|
415
|
-
decision === "always_allow_high_risk" ||
|
|
416
415
|
decision === "temporary_override";
|
|
417
416
|
|
|
418
417
|
if (isAllow) {
|
|
419
418
|
// Prefer allow_always for persistent decisions, fallback to allow_once
|
|
420
|
-
if (decision === "always_allow"
|
|
419
|
+
if (decision === "always_allow") {
|
|
421
420
|
const alwaysOpt = options.find((o) => o.kind === "allow_always");
|
|
422
421
|
if (alwaysOpt) return alwaysOpt.optionId;
|
|
423
422
|
}
|
package/src/agent/loop.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/node";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { LLMCallSite } from "../config/schemas/llm.js";
|
|
4
|
+
import {
|
|
5
|
+
estimatePromptTokensRaw,
|
|
6
|
+
estimateToolsTokens,
|
|
7
|
+
getCalibrationProviderKey,
|
|
8
|
+
} from "../context/token-estimator.js";
|
|
4
9
|
import { truncateOversizedToolResults } from "../context/tool-result-truncation.js";
|
|
5
10
|
import { getHookManager } from "../hooks/manager.js";
|
|
6
11
|
import type {
|
|
@@ -15,7 +20,9 @@ import {
|
|
|
15
20
|
applyStreamingSubstitution,
|
|
16
21
|
applySubstitutions,
|
|
17
22
|
} from "../tools/sensitive-output-placeholders.js";
|
|
23
|
+
import { ProviderError } from "../util/errors.js";
|
|
18
24
|
import { getLogger } from "../util/logger.js";
|
|
25
|
+
import { isRetryableNetworkError } from "../util/retry.js";
|
|
19
26
|
|
|
20
27
|
const log = getLogger("agent-loop");
|
|
21
28
|
|
|
@@ -23,7 +30,7 @@ export interface AgentLoopConfig {
|
|
|
23
30
|
maxTokens: number;
|
|
24
31
|
maxInputTokens?: number; // context window size for tool result truncation
|
|
25
32
|
thinking?: { enabled: boolean };
|
|
26
|
-
effort: "low" | "medium" | "high" | "max";
|
|
33
|
+
effort: "low" | "medium" | "high" | "xhigh" | "max";
|
|
27
34
|
speed?: "standard" | "fast";
|
|
28
35
|
toolChoice?:
|
|
29
36
|
| { type: "auto" }
|
|
@@ -100,6 +107,13 @@ export type AgentEvent =
|
|
|
100
107
|
providerDurationMs: number;
|
|
101
108
|
rawRequest?: unknown;
|
|
102
109
|
rawResponse?: unknown;
|
|
110
|
+
/**
|
|
111
|
+
* Pre-send token estimate for the same call. Used by the estimator
|
|
112
|
+
* calibrator to learn how off the heuristic is versus provider
|
|
113
|
+
* ground truth. Omitted only when estimation genuinely was not run
|
|
114
|
+
* for this call (e.g. legacy/stubbed code paths).
|
|
115
|
+
*/
|
|
116
|
+
estimatedInputTokens?: number;
|
|
103
117
|
};
|
|
104
118
|
|
|
105
119
|
const DEFAULT_CONFIG: AgentLoopConfig = {
|
|
@@ -111,6 +125,42 @@ const DEFAULT_CONFIG: AgentLoopConfig = {
|
|
|
111
125
|
const MAX_CONSECUTIVE_ERROR_NUDGES = 3;
|
|
112
126
|
const MAX_EMPTY_RESPONSE_RETRIES = 1;
|
|
113
127
|
|
|
128
|
+
/**
|
|
129
|
+
* User-config HTTP status codes that should never page the on-call: billing
|
|
130
|
+
* exhaustion (402), invalid credentials (401), and forbidden/plan-gated (403).
|
|
131
|
+
* The user-facing error path already surfaces an actionable message (e.g.
|
|
132
|
+
* credits_exhausted); a Sentry issue adds noise without engineering signal.
|
|
133
|
+
*/
|
|
134
|
+
const USER_CONFIG_STATUS_CODES = new Set([401, 402, 403]);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Whether an agent-loop error should be reported to Sentry. Suppresses:
|
|
138
|
+
*
|
|
139
|
+
* - `ProviderError` carrying a user-config status code (401/402/403) — these
|
|
140
|
+
* are bad API keys, exhausted billing, or plan gates, not engineering bugs.
|
|
141
|
+
* - Retry-exhausted transient network errors (`retriesExhausted === true` +
|
|
142
|
+
* still categorized as retryable network) — the retry loop already tried
|
|
143
|
+
* its best; the user's network was flaky, not our code.
|
|
144
|
+
*
|
|
145
|
+
* Everything else (5xx with no retry-exhaustion tag, surprise errors, tool
|
|
146
|
+
* failures, etc.) still pages.
|
|
147
|
+
*/
|
|
148
|
+
export function shouldCaptureAgentLoopError(err: Error): boolean {
|
|
149
|
+
if (
|
|
150
|
+
err instanceof ProviderError &&
|
|
151
|
+
err.statusCode !== undefined &&
|
|
152
|
+
USER_CONFIG_STATUS_CODES.has(err.statusCode)
|
|
153
|
+
) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
const exhausted = (err as Error & { retriesExhausted?: boolean })
|
|
157
|
+
.retriesExhausted;
|
|
158
|
+
if (exhausted === true && isRetryableNetworkError(err)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
114
164
|
export interface ResolvedSystemPrompt {
|
|
115
165
|
systemPrompt: string;
|
|
116
166
|
maxTokens?: number;
|
|
@@ -204,8 +254,10 @@ export class AgentLoop {
|
|
|
204
254
|
signal?: AbortSignal,
|
|
205
255
|
requestId?: string,
|
|
206
256
|
onCheckpoint?: (checkpoint: CheckpointInfo) => CheckpointDecision,
|
|
257
|
+
callSite?: LLMCallSite,
|
|
207
258
|
): Promise<Message[]> {
|
|
208
259
|
const history = [...messages];
|
|
260
|
+
const initialHistoryLength = messages.length;
|
|
209
261
|
let toolUseTurns = 0;
|
|
210
262
|
let consecutiveErrorTurns = 0;
|
|
211
263
|
let emptyResponseRetries = 0;
|
|
@@ -221,6 +273,11 @@ export class AgentLoop {
|
|
|
221
273
|
while (true) {
|
|
222
274
|
if (signal?.aborted) break;
|
|
223
275
|
|
|
276
|
+
rlog.info(
|
|
277
|
+
{ turn: toolUseTurns, messageCount: history.length },
|
|
278
|
+
"Agent loop iteration start",
|
|
279
|
+
);
|
|
280
|
+
|
|
224
281
|
let toolUseBlocks: Extract<ContentBlock, { type: "tool_use" }>[] = [];
|
|
225
282
|
|
|
226
283
|
try {
|
|
@@ -235,25 +292,47 @@ export class AgentLoop {
|
|
|
235
292
|
? this.resolveSystemPrompt(history)
|
|
236
293
|
: null;
|
|
237
294
|
const turnSystemPrompt = resolved?.systemPrompt ?? this.systemPrompt;
|
|
238
|
-
const turnMaxTokens = resolved?.maxTokens ?? this.config.maxTokens;
|
|
239
295
|
const turnModel = resolved?.model;
|
|
240
296
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
297
|
+
// Field precedence (highest wins):
|
|
298
|
+
// 1. Per-turn explicit (`resolved.maxTokens` / `resolved.model`)
|
|
299
|
+
// 2. Call-site resolved values (filled by
|
|
300
|
+
// `RetryProvider.normalizeSendMessageOptions` from
|
|
301
|
+
// `resolveCallSiteConfig(callSite, llm)`)
|
|
302
|
+
// 3. Conversation defaults (`this.config.*`, sourced from
|
|
303
|
+
// `llm.default`)
|
|
304
|
+
//
|
|
305
|
+
// When `callSite` is present we deliberately leave
|
|
306
|
+
// `max_tokens`/`thinking`/`effort`/`speed` *unset* in `providerConfig`
|
|
307
|
+
// so the normalizer can fill them from the call-site resolution. The
|
|
308
|
+
// normalizer only writes these fields when they're undefined; if we
|
|
309
|
+
// pre-set them from `this.config` here, every per-call-site override
|
|
310
|
+
// for these knobs is silently ignored.
|
|
311
|
+
//
|
|
312
|
+
// `toolChoice` and `cacheTtl` are not part of the call-site schema, so
|
|
313
|
+
// they always come from `this.config` regardless of `callSite`.
|
|
314
|
+
const providerConfig: Record<string, unknown> = {};
|
|
315
|
+
|
|
316
|
+
if (resolved?.maxTokens !== undefined) {
|
|
317
|
+
providerConfig.max_tokens = resolved.maxTokens;
|
|
318
|
+
} else if (!callSite) {
|
|
319
|
+
providerConfig.max_tokens = this.config.maxTokens;
|
|
249
320
|
}
|
|
250
321
|
|
|
251
|
-
if (
|
|
252
|
-
providerConfig.
|
|
322
|
+
if (turnModel) {
|
|
323
|
+
providerConfig.model = turnModel;
|
|
253
324
|
}
|
|
254
325
|
|
|
255
|
-
if (
|
|
256
|
-
|
|
326
|
+
if (!callSite) {
|
|
327
|
+
if (this.config.thinking?.enabled) {
|
|
328
|
+
providerConfig.thinking = { type: "adaptive" };
|
|
329
|
+
}
|
|
330
|
+
if (this.config.effort) {
|
|
331
|
+
providerConfig.effort = this.config.effort;
|
|
332
|
+
}
|
|
333
|
+
if (this.config.speed && this.config.speed !== "standard") {
|
|
334
|
+
providerConfig.speed = this.config.speed;
|
|
335
|
+
}
|
|
257
336
|
}
|
|
258
337
|
|
|
259
338
|
if (this.config.toolChoice) {
|
|
@@ -264,6 +343,17 @@ export class AgentLoop {
|
|
|
264
343
|
providerConfig.cacheTtl = this.config.cacheTtl;
|
|
265
344
|
}
|
|
266
345
|
|
|
346
|
+
// Per-call LLM call-site identifier. Surfaces on the per-call
|
|
347
|
+
// `config.callSite` so `RetryProvider.normalizeSendMessageOptions`
|
|
348
|
+
// can route through `resolveCallSiteConfig` against
|
|
349
|
+
// `llm.callSites.<id>` (falling back to `llm.default` when absent).
|
|
350
|
+
// User-initiated conversation turns default to `mainAgent` in the
|
|
351
|
+
// agent loop's caller; other invocation contexts (heartbeat, filing,
|
|
352
|
+
// analyze, etc.) pass their own `callSite`.
|
|
353
|
+
if (callSite) {
|
|
354
|
+
providerConfig.callSite = callSite;
|
|
355
|
+
}
|
|
356
|
+
|
|
267
357
|
const preLlmResult = await getHookManager().trigger("pre-llm-call", {
|
|
268
358
|
systemPrompt: turnSystemPrompt,
|
|
269
359
|
messages: history,
|
|
@@ -292,6 +382,25 @@ export class AgentLoop {
|
|
|
292
382
|
const providerStart = Date.now();
|
|
293
383
|
lastLlmCallTime = providerStart;
|
|
294
384
|
|
|
385
|
+
// Compute the pre-send estimate against the full in-memory
|
|
386
|
+
// history — matching what upstream callers of
|
|
387
|
+
// `estimatePromptTokens` (preflight, mid-loop checkpoints, the
|
|
388
|
+
// window manager) see. We use the RAW estimate (before applying
|
|
389
|
+
// the existing correction) so the calibrator learns the true
|
|
390
|
+
// bias against provider ground truth instead of ratcheting a
|
|
391
|
+
// feedback loop against its own corrected output.
|
|
392
|
+
const toolTokenBudget =
|
|
393
|
+
currentTools.length > 0 ? estimateToolsTokens(currentTools) : 0;
|
|
394
|
+
const preSendEstimatedTokens = estimatePromptTokensRaw(
|
|
395
|
+
history,
|
|
396
|
+
turnSystemPrompt,
|
|
397
|
+
{
|
|
398
|
+
providerName: getCalibrationProviderKey(this.provider),
|
|
399
|
+
toolTokenBudget,
|
|
400
|
+
},
|
|
401
|
+
);
|
|
402
|
+
rlog.info({ turn: toolUseTurns }, "LLM call start");
|
|
403
|
+
|
|
295
404
|
// Strip image contentBlocks from older tool results to prevent
|
|
296
405
|
// screenshots from accumulating in the context window. The LLM
|
|
297
406
|
// already saw each image on the turn it was captured; keeping
|
|
@@ -372,6 +481,7 @@ export class AgentLoop {
|
|
|
372
481
|
providerDurationMs,
|
|
373
482
|
rawRequest: response.rawRequest,
|
|
374
483
|
rawResponse: response.rawResponse,
|
|
484
|
+
estimatedInputTokens: preSendEstimatedTokens,
|
|
375
485
|
});
|
|
376
486
|
|
|
377
487
|
void getHookManager().trigger("post-llm-call", {
|
|
@@ -407,17 +517,65 @@ export class AgentLoop {
|
|
|
407
517
|
block.type === "tool_use",
|
|
408
518
|
);
|
|
409
519
|
|
|
520
|
+
rlog.info(
|
|
521
|
+
{
|
|
522
|
+
turn: toolUseTurns,
|
|
523
|
+
stopReason: response.stopReason,
|
|
524
|
+
contentBlocks: response.content.length,
|
|
525
|
+
toolUseCount: toolUseBlocks.length,
|
|
526
|
+
durationMs: providerDurationMs,
|
|
527
|
+
},
|
|
528
|
+
"LLM call complete",
|
|
529
|
+
);
|
|
530
|
+
|
|
410
531
|
// Detect empty responses: no user-visible text and no tool calls.
|
|
411
532
|
// This can happen when the model fails to produce output after
|
|
412
533
|
// receiving a large tool result. Retry once with a nudge before
|
|
413
534
|
// the message is persisted.
|
|
535
|
+
//
|
|
536
|
+
// Only nudge when the model hasn't already delivered text to the user
|
|
537
|
+
// earlier in this tool-use chain. If a prior assistant turn in history
|
|
538
|
+
// contained visible text (e.g. the model said its piece before calling
|
|
539
|
+
// a side-effect tool like `remember`), an empty follow-up is the model
|
|
540
|
+
// correctly ending its turn — nudging would mislead it into thinking
|
|
541
|
+
// its earlier text didn't land and cause a verbatim re-send.
|
|
542
|
+
//
|
|
543
|
+
// Note: we check ANY prior assistant turn from this run()
|
|
544
|
+
// invocation, not just the most recent one. In multi-step tool-use
|
|
545
|
+
// chains (say-something → call-tool → call-another-tool → end),
|
|
546
|
+
// the "say-something" text lives on an earlier assistant turn while
|
|
547
|
+
// the most recent assistant turn is a pure tool_use with no text.
|
|
548
|
+
// Restricting the check to the most recent assistant turn would
|
|
549
|
+
// falsely nudge in that case and trigger a duplicate re-send of
|
|
550
|
+
// text the user already saw.
|
|
551
|
+
//
|
|
552
|
+
// Scope the scan to messages appended during this run() call only.
|
|
553
|
+
// Assistant text from prior conversation turns (earlier run()
|
|
554
|
+
// invocations passed in via `messages`) must NOT suppress the
|
|
555
|
+
// nudge — those turns completed long ago and have no bearing on
|
|
556
|
+
// whether the current tool-use chain has delivered text yet.
|
|
414
557
|
const hasVisibleText = response.content.some(
|
|
415
558
|
(block) => block.type === "text" && block.text.trim().length > 0,
|
|
416
559
|
);
|
|
560
|
+
const priorAssistantHadVisibleText = (() => {
|
|
561
|
+
for (let i = history.length - 1; i >= initialHistoryLength; i--) {
|
|
562
|
+
const msg = history[i];
|
|
563
|
+
if (msg.role !== "assistant") continue;
|
|
564
|
+
const hasText = msg.content.some(
|
|
565
|
+
(block) =>
|
|
566
|
+
block.type === "text" &&
|
|
567
|
+
typeof (block as { text?: unknown }).text === "string" &&
|
|
568
|
+
(block as { text: string }).text.trim().length > 0,
|
|
569
|
+
);
|
|
570
|
+
if (hasText) return true;
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
})();
|
|
417
574
|
if (
|
|
418
575
|
!hasVisibleText &&
|
|
419
576
|
toolUseBlocks.length === 0 &&
|
|
420
577
|
toolUseTurns > 0 &&
|
|
578
|
+
!priorAssistantHadVisibleText &&
|
|
421
579
|
emptyResponseRetries < MAX_EMPTY_RESPONSE_RETRIES
|
|
422
580
|
) {
|
|
423
581
|
emptyResponseRetries++;
|
|
@@ -437,7 +595,12 @@ export class AgentLoop {
|
|
|
437
595
|
continue;
|
|
438
596
|
}
|
|
439
597
|
|
|
440
|
-
if (
|
|
598
|
+
if (
|
|
599
|
+
!hasVisibleText &&
|
|
600
|
+
toolUseBlocks.length === 0 &&
|
|
601
|
+
toolUseTurns > 0 &&
|
|
602
|
+
!priorAssistantHadVisibleText
|
|
603
|
+
) {
|
|
441
604
|
rlog.error(
|
|
442
605
|
{ turn: toolUseTurns, retries: emptyResponseRetries },
|
|
443
606
|
"Model returned empty response after tool results — retries exhausted",
|
|
@@ -479,6 +642,15 @@ export class AgentLoop {
|
|
|
479
642
|
// Execute all tools concurrently for reduced latency.
|
|
480
643
|
// Race against the abort signal so cancellation isn't blocked by
|
|
481
644
|
// stuck tools (e.g. a hung browser navigation).
|
|
645
|
+
const toolExecStart = Date.now();
|
|
646
|
+
rlog.info(
|
|
647
|
+
{
|
|
648
|
+
turn: toolUseTurns,
|
|
649
|
+
toolNames: toolUseBlocks.map((t) => t.name),
|
|
650
|
+
},
|
|
651
|
+
"Tool execution start",
|
|
652
|
+
);
|
|
653
|
+
|
|
482
654
|
const toolExecutionPromise = Promise.all(
|
|
483
655
|
toolUseBlocks.map(async (toolUse) => {
|
|
484
656
|
const result = await this.toolExecutor!(
|
|
@@ -522,6 +694,15 @@ export class AgentLoop {
|
|
|
522
694
|
toolResults = await toolExecutionPromise;
|
|
523
695
|
}
|
|
524
696
|
|
|
697
|
+
rlog.info(
|
|
698
|
+
{
|
|
699
|
+
turn: toolUseTurns,
|
|
700
|
+
toolCount: toolResults.length,
|
|
701
|
+
durationMs: Date.now() - toolExecStart,
|
|
702
|
+
},
|
|
703
|
+
"Tool execution complete",
|
|
704
|
+
);
|
|
705
|
+
|
|
525
706
|
// Merge sensitive output bindings from tool results into the
|
|
526
707
|
// per-run substitution map. Bindings carry placeholder->value pairs
|
|
527
708
|
// that are resolved in streamed text deltas and final message text.
|
|
@@ -653,12 +834,23 @@ export class AgentLoop {
|
|
|
653
834
|
{ err, turn: toolUseTurns, messageCount: history.length },
|
|
654
835
|
"Agent loop error during turn processing",
|
|
655
836
|
);
|
|
656
|
-
|
|
837
|
+
if (shouldCaptureAgentLoopError(err)) {
|
|
838
|
+
Sentry.captureException(err);
|
|
839
|
+
}
|
|
657
840
|
onEvent({ type: "error", error: err });
|
|
658
841
|
break;
|
|
659
842
|
}
|
|
660
843
|
}
|
|
661
844
|
|
|
845
|
+
rlog.info(
|
|
846
|
+
{
|
|
847
|
+
turns: toolUseTurns,
|
|
848
|
+
finalMessageCount: history.length,
|
|
849
|
+
aborted: signal?.aborted ?? false,
|
|
850
|
+
},
|
|
851
|
+
"Agent loop exited",
|
|
852
|
+
);
|
|
853
|
+
|
|
662
854
|
return history;
|
|
663
855
|
}
|
|
664
856
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for resvg-lazy and the writeTraitsAndRenderAvatar fallback it feeds.
|
|
3
|
+
*
|
|
4
|
+
* Covers the graceful-degradation path when the native @resvg/resvg-js binding
|
|
5
|
+
* is missing (e.g. bun install skipped the platform-specific optional
|
|
6
|
+
* dependency). The lazy loader must warn once and report unavailability, and
|
|
7
|
+
* writeTraitsAndRenderAvatar must return `native_unavailable` so the HTTP
|
|
8
|
+
* layer can respond 503 instead of 500.
|
|
9
|
+
*
|
|
10
|
+
* Bun's `mock.module` evaluates factories eagerly on re-registration, which
|
|
11
|
+
* makes it awkward to flip a module between "throws" and "returns fake
|
|
12
|
+
* export" across tests in the same file. We instead install a single
|
|
13
|
+
* throwing mock at module scope for the require-path test, and use the
|
|
14
|
+
* `__setResvgCacheForTests` / `__resetResvgCacheForTests` hooks to drive the
|
|
15
|
+
* rest of the cases deterministically.
|
|
16
|
+
*/
|
|
17
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
18
|
+
|
|
19
|
+
type LogCall = { bindings: unknown; msg: string };
|
|
20
|
+
const warnCalls: LogCall[] = [];
|
|
21
|
+
|
|
22
|
+
mock.module("../util/logger.js", () => ({
|
|
23
|
+
getLogger: () => ({
|
|
24
|
+
warn: (bindings: unknown, msg: string) => {
|
|
25
|
+
warnCalls.push({ bindings, msg });
|
|
26
|
+
},
|
|
27
|
+
info: () => {},
|
|
28
|
+
error: () => {},
|
|
29
|
+
debug: () => {},
|
|
30
|
+
trace: () => {},
|
|
31
|
+
fatal: () => {},
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
mock.module("../util/platform.js", () => ({
|
|
36
|
+
AVATAR_IMAGE_FILENAME: "avatar-image.png",
|
|
37
|
+
getAvatarDir: () => "/tmp/vellum-test-avatar-never-written",
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// Install a throwing @resvg/resvg-js mock at module scope. Bun only calls
|
|
41
|
+
// this factory the first time the module is required within the test worker;
|
|
42
|
+
// later `require` calls return whatever was produced by that first call (or
|
|
43
|
+
// throw, if the factory threw). That's exactly what we want for the
|
|
44
|
+
// "require fails" test below.
|
|
45
|
+
mock.module("@resvg/resvg-js", () => {
|
|
46
|
+
throw new Error("Cannot require module @resvg/resvg-js-darwin-x64");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("resvg-lazy — require failure path", () => {
|
|
50
|
+
beforeEach(async () => {
|
|
51
|
+
warnCalls.length = 0;
|
|
52
|
+
const { __resetResvgCacheForTests } = await import("./resvg-lazy.js");
|
|
53
|
+
__resetResvgCacheForTests();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("real require failure triggers warn log with platform context", async () => {
|
|
57
|
+
const { isResvgAvailable } = await import("./resvg-lazy.js");
|
|
58
|
+
|
|
59
|
+
// Call twice — caching must prevent a duplicate warn.
|
|
60
|
+
expect(isResvgAvailable()).toBe(false);
|
|
61
|
+
expect(isResvgAvailable()).toBe(false);
|
|
62
|
+
|
|
63
|
+
expect(warnCalls.length).toBe(1);
|
|
64
|
+
const call = warnCalls[0]!;
|
|
65
|
+
const bindings = call.bindings as Record<string, unknown>;
|
|
66
|
+
expect(bindings.platform).toBe(process.platform);
|
|
67
|
+
expect(bindings.arch).toBe(process.arch);
|
|
68
|
+
expect(String(bindings.module)).toContain("@resvg/resvg-js-");
|
|
69
|
+
expect(bindings.err).toBeInstanceOf(Error);
|
|
70
|
+
expect(call.msg).toContain("@resvg/resvg-js");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("resvg-lazy — API shape when unavailable", () => {
|
|
75
|
+
beforeEach(async () => {
|
|
76
|
+
warnCalls.length = 0;
|
|
77
|
+
const { __setResvgCacheForTests } = await import("./resvg-lazy.js");
|
|
78
|
+
__setResvgCacheForTests({
|
|
79
|
+
available: false,
|
|
80
|
+
error: new Error("Cannot require module @resvg/resvg-js-darwin-x64"),
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("isResvgAvailable returns false", async () => {
|
|
85
|
+
const { isResvgAvailable } = await import("./resvg-lazy.js");
|
|
86
|
+
expect(isResvgAvailable()).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("getResvg throws the underlying error", async () => {
|
|
90
|
+
const { getResvg } = await import("./resvg-lazy.js");
|
|
91
|
+
expect(() => getResvg()).toThrow(
|
|
92
|
+
/Cannot require module @resvg\/resvg-js-darwin-x64/,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("writeTraitsAndRenderAvatar — native module missing", () => {
|
|
98
|
+
beforeEach(async () => {
|
|
99
|
+
warnCalls.length = 0;
|
|
100
|
+
const { __setResvgCacheForTests } = await import("./resvg-lazy.js");
|
|
101
|
+
__setResvgCacheForTests({
|
|
102
|
+
available: false,
|
|
103
|
+
error: new Error("Cannot require module @resvg/resvg-js-darwin-x64"),
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("returns native_unavailable without attempting disk writes", async () => {
|
|
108
|
+
const { writeTraitsAndRenderAvatar } = await import("./traits-png-sync.js");
|
|
109
|
+
|
|
110
|
+
const result = writeTraitsAndRenderAvatar({
|
|
111
|
+
bodyShape: "blob",
|
|
112
|
+
eyeStyle: "curious",
|
|
113
|
+
color: "green",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(result.ok).toBe(false);
|
|
117
|
+
if (result.ok) return; // narrow for TypeScript
|
|
118
|
+
expect(result.reason).toBe("native_unavailable");
|
|
119
|
+
expect(result.message).toContain("@resvg/resvg-js");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("returns invalid_traits (not native_unavailable) when traits are bad", async () => {
|
|
123
|
+
const { writeTraitsAndRenderAvatar } = await import("./traits-png-sync.js");
|
|
124
|
+
|
|
125
|
+
// Empty traits object fails the shape check before we ever consult resvg.
|
|
126
|
+
const result = writeTraitsAndRenderAvatar({
|
|
127
|
+
bodyShape: "",
|
|
128
|
+
eyeStyle: "",
|
|
129
|
+
color: "",
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(result.ok).toBe(false);
|
|
133
|
+
if (result.ok) return;
|
|
134
|
+
expect(result.reason).toBe("invalid_traits");
|
|
135
|
+
});
|
|
136
|
+
});
|
package/src/avatar/resvg-lazy.ts
CHANGED
|
@@ -1,21 +1,94 @@
|
|
|
1
1
|
import type { Resvg as ResvgType } from "@resvg/resvg-js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { getLogger } from "../util/logger.js";
|
|
4
|
+
|
|
5
|
+
const log = getLogger("resvg-lazy");
|
|
6
|
+
|
|
7
|
+
type ResvgLoadResult =
|
|
8
|
+
| { available: true; Resvg: typeof ResvgType }
|
|
9
|
+
| { available: false; error: Error };
|
|
10
|
+
|
|
11
|
+
let cached: ResvgLoadResult | undefined;
|
|
12
|
+
|
|
13
|
+
function loadResvg(): ResvgLoadResult {
|
|
14
|
+
try {
|
|
15
|
+
// Inline require is necessary here: @resvg/resvg-js loads a platform-specific
|
|
16
|
+
// native .node addon at import time. A top-level import would crash the daemon
|
|
17
|
+
// on startup inside bun --compile binaries where native addons are unavailable.
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const mod = require("@resvg/resvg-js") as typeof import("@resvg/resvg-js");
|
|
20
|
+
return { available: true, Resvg: mod.Resvg };
|
|
21
|
+
} catch (err) {
|
|
22
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
23
|
+
// Log once at warn level (not error) — the daemon should keep running and
|
|
24
|
+
// callers should fall back to a non-native path (ASCII only, or a 503 at
|
|
25
|
+
// the HTTP layer). Emitting at error level would page Sentry on every
|
|
26
|
+
// install that skipped the platform-specific optional dependency.
|
|
27
|
+
log.warn(
|
|
28
|
+
{
|
|
29
|
+
err: error,
|
|
30
|
+
platform: process.platform,
|
|
31
|
+
arch: process.arch,
|
|
32
|
+
module: `@resvg/resvg-js-${process.platform}-${process.arch}`,
|
|
33
|
+
},
|
|
34
|
+
"Failed to load @resvg/resvg-js native module — avatar PNG rendering will be unavailable. " +
|
|
35
|
+
"The platform-specific optional dependency is likely missing.",
|
|
36
|
+
);
|
|
37
|
+
return { available: false, error };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getLoadResult(): ResvgLoadResult {
|
|
42
|
+
if (!cached) {
|
|
43
|
+
cached = loadResvg();
|
|
44
|
+
}
|
|
45
|
+
return cached;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns `true` if the native @resvg/resvg-js binding loaded successfully on
|
|
50
|
+
* first access. Callers should check this before calling `getResvg()` and fall
|
|
51
|
+
* back to a non-native path (e.g. ASCII-only rendering, or a 503 at the HTTP
|
|
52
|
+
* layer) when it is `false`.
|
|
53
|
+
*
|
|
54
|
+
* Loading is attempted lazily on first access and the result is cached, so
|
|
55
|
+
* calling this multiple times is cheap and does not re-emit warnings.
|
|
56
|
+
*/
|
|
57
|
+
export function isResvgAvailable(): boolean {
|
|
58
|
+
return getLoadResult().available;
|
|
59
|
+
}
|
|
4
60
|
|
|
5
61
|
/**
|
|
6
62
|
* Returns the Resvg constructor, loading the native module on first call.
|
|
7
63
|
* Defers the native-addon require so the daemon can start even when the
|
|
8
64
|
* platform-specific binary is unavailable (e.g. inside a bun --compile
|
|
9
65
|
* single-file executable).
|
|
66
|
+
*
|
|
67
|
+
* Throws if the native module could not be loaded. Callers that need to
|
|
68
|
+
* degrade gracefully should check `isResvgAvailable()` first.
|
|
10
69
|
*/
|
|
11
70
|
export function getResvg(): typeof ResvgType {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// on startup inside bun --compile binaries where native addons are unavailable.
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
17
|
-
const mod = require("@resvg/resvg-js") as typeof import("@resvg/resvg-js");
|
|
18
|
-
ResvgClass = mod.Resvg;
|
|
71
|
+
const result = getLoadResult();
|
|
72
|
+
if (!result.available) {
|
|
73
|
+
throw result.error;
|
|
19
74
|
}
|
|
20
|
-
return
|
|
75
|
+
return result.Resvg;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Test-only hook to reset the cached load result between test cases. Do not
|
|
80
|
+
* call from production code.
|
|
81
|
+
*/
|
|
82
|
+
export function __resetResvgCacheForTests(): void {
|
|
83
|
+
cached = undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Test-only hook to force the cached load result to a specific state without
|
|
88
|
+
* exercising the real `require`. Useful for asserting the unavailable path
|
|
89
|
+
* without depending on Bun's module-mock behavior (which re-imports the real
|
|
90
|
+
* module after the factory throws once).
|
|
91
|
+
*/
|
|
92
|
+
export function __setResvgCacheForTests(result: ResvgLoadResult): void {
|
|
93
|
+
cached = result;
|
|
21
94
|
}
|
|
@@ -7,6 +7,7 @@ import { AVATAR_IMAGE_FILENAME, getAvatarDir } from "../util/platform.js";
|
|
|
7
7
|
import { renderCharacterAscii } from "./ascii-renderer.js";
|
|
8
8
|
import { getCharacterComponents } from "./character-components.js";
|
|
9
9
|
import { renderCharacterPng } from "./png-renderer.js";
|
|
10
|
+
import { isResvgAvailable } from "./resvg-lazy.js";
|
|
10
11
|
|
|
11
12
|
const log = getLogger("traits-png-sync");
|
|
12
13
|
|
|
@@ -20,7 +21,7 @@ export type TraitsSyncResult =
|
|
|
20
21
|
| { ok: true; asciiWritten: boolean }
|
|
21
22
|
| {
|
|
22
23
|
ok: false;
|
|
23
|
-
reason: "invalid_traits" | "render_error";
|
|
24
|
+
reason: "invalid_traits" | "render_error" | "native_unavailable";
|
|
24
25
|
message: string;
|
|
25
26
|
};
|
|
26
27
|
|
|
@@ -144,6 +145,25 @@ export function writeTraitsAndRenderAvatar(
|
|
|
144
145
|
};
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
// Short-circuit before touching disk when the native rasterizer is missing.
|
|
149
|
+
// Both PNG and ASCII rendering route through @resvg/resvg-js, so without it
|
|
150
|
+
// we cannot produce either artifact. Callers translate this into a 503 so
|
|
151
|
+
// the HTTP route returns an actionable status rather than a 500.
|
|
152
|
+
if (!isResvgAvailable()) {
|
|
153
|
+
log.warn(
|
|
154
|
+
{ traits },
|
|
155
|
+
"Skipping avatar render — native @resvg/resvg-js binding is unavailable",
|
|
156
|
+
);
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
reason: "native_unavailable",
|
|
160
|
+
message:
|
|
161
|
+
"Avatar PNG rendering is unavailable on this platform because the " +
|
|
162
|
+
"@resvg/resvg-js native binding failed to load. Reinstall dependencies " +
|
|
163
|
+
"to pull the platform-specific optional package.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
147
167
|
const avatarDir = getAvatarDir();
|
|
148
168
|
const traitsPath = join(avatarDir, "character-traits.json");
|
|
149
169
|
|