@vellumai/assistant 0.4.52 → 0.4.54
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/ARCHITECTURE.md +2 -2
- package/bun.lock +62 -349
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/keychain-broker.md +91 -40
- package/docs/architecture/memory.md +3 -3
- package/docs/architecture/security.md +2 -2
- package/knip.json +7 -29
- package/package.json +2 -9
- package/src/__tests__/agent-loop.test.ts +1 -1
- package/src/__tests__/app-git-history.test.ts +0 -2
- package/src/__tests__/app-git-service.test.ts +1 -6
- package/src/__tests__/approval-cascade.test.ts +3 -2
- package/src/__tests__/approval-routes-http.test.ts +0 -1
- package/src/__tests__/asset-materialize-tool.test.ts +0 -1
- package/src/__tests__/asset-search-tool.test.ts +0 -1
- package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
- package/src/__tests__/attachments-store.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +5 -1
- package/src/__tests__/browser-fill-credential.test.ts +4 -6
- package/src/__tests__/btw-routes.test.ts +39 -0
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +1 -1
- package/src/__tests__/call-routes-http.test.ts +1 -3
- package/src/__tests__/canonical-guardian-store.test.ts +33 -2
- package/src/__tests__/channel-guardian.test.ts +4 -4
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +1 -1
- package/src/__tests__/checker.test.ts +13 -11
- package/src/__tests__/claude-code-skill-regression.test.ts +5 -2
- package/src/__tests__/claude-code-tool-profiles.test.ts +7 -3
- package/src/__tests__/config-loader-backfill.test.ts +1 -5
- package/src/__tests__/config-schema.test.ts +9 -46
- package/src/__tests__/config-watcher.test.ts +11 -3
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
- package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
- package/src/__tests__/credential-broker-server-use.test.ts +76 -40
- package/src/__tests__/credential-security-e2e.test.ts +1 -6
- package/src/__tests__/credential-security-invariants.test.ts +27 -8
- package/src/__tests__/credential-vault-unit.test.ts +32 -16
- package/src/__tests__/credential-vault.test.ts +40 -28
- package/src/__tests__/credentials-cli.test.ts +1 -21
- package/src/__tests__/email-invite-adapter.test.ts +0 -1
- package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
- package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
- package/src/__tests__/gateway-only-enforcement.test.ts +1 -23
- package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
- package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
- package/src/__tests__/guardian-action-store.test.ts +0 -57
- package/src/__tests__/guardian-outbound-http.test.ts +1 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
- package/src/__tests__/hooks-blocking.test.ts +1 -1
- package/src/__tests__/hooks-config.test.ts +5 -29
- package/src/__tests__/hooks-discovery.test.ts +1 -1
- package/src/__tests__/hooks-integration.test.ts +1 -1
- package/src/__tests__/hooks-manager.test.ts +1 -1
- package/src/__tests__/hooks-runner.test.ts +1 -23
- package/src/__tests__/hooks-settings.test.ts +1 -1
- package/src/__tests__/hooks-templates.test.ts +1 -1
- package/src/__tests__/host-shell-tool.test.ts +0 -1
- package/src/__tests__/http-user-message-parity.test.ts +19 -0
- package/src/__tests__/integration-status.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +0 -3
- package/src/__tests__/list-messages-attachments.test.ts +0 -1
- package/src/__tests__/llm-usage-store.test.ts +50 -0
- package/src/__tests__/log-export-workspace.test.ts +233 -0
- package/src/__tests__/managed-proxy-context.test.ts +41 -41
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/media-generate-image.test.ts +9 -4
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -7
- package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
- package/src/__tests__/memory-regressions.test.ts +27 -28
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
- package/src/__tests__/migration-export-http.test.ts +0 -1
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
- package/src/__tests__/migration-validate-http.test.ts +0 -1
- package/src/__tests__/notification-decision-fallback.test.ts +1 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
- package/src/__tests__/oauth-cli.test.ts +2 -14
- package/src/__tests__/oauth-store.test.ts +3 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
- package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -2
- package/src/__tests__/openai-provider.test.ts +7 -7
- package/src/__tests__/platform.test.ts +14 -4
- package/src/__tests__/pricing.test.ts +0 -234
- package/src/__tests__/provider-commit-message-generator.test.ts +19 -15
- package/src/__tests__/provider-fail-open-selection.test.ts +67 -62
- package/src/__tests__/provider-managed-proxy-integration.test.ts +88 -85
- package/src/__tests__/provider-registry-ollama.test.ts +10 -4
- package/src/__tests__/public-ingress-urls.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/registry.test.ts +3 -103
- package/src/__tests__/relay-server.test.ts +0 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
- package/src/__tests__/runtime-events-sse.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
- package/src/__tests__/secret-onetime-send.test.ts +1 -6
- package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -14
- package/src/__tests__/secret-scanner-executor.test.ts +0 -1
- package/src/__tests__/secure-keys.test.ts +241 -229
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/session-abort-tool-results.test.ts +3 -2
- package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
- package/src/__tests__/session-agent-loop.test.ts +2 -2
- package/src/__tests__/session-confirmation-signals.test.ts +3 -2
- package/src/__tests__/session-error.test.ts +5 -4
- package/src/__tests__/session-history-web-search.test.ts +34 -9
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
- package/src/__tests__/session-pre-run-repair.test.ts +3 -2
- package/src/__tests__/session-provider-retry-repair.test.ts +31 -27
- package/src/__tests__/session-queue.test.ts +5 -5
- package/src/__tests__/session-runtime-assembly.test.ts +118 -0
- package/src/__tests__/session-slash-known.test.ts +31 -14
- package/src/__tests__/session-slash-queue.test.ts +3 -2
- package/src/__tests__/session-slash-unknown.test.ts +3 -2
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
- package/src/__tests__/session-workspace-injection.test.ts +3 -2
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -2
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/skillssh-registry.test.ts +21 -0
- package/src/__tests__/slack-channel-config.test.ts +1 -7
- package/src/__tests__/slack-share-routes.test.ts +1 -1
- package/src/__tests__/swarm-recursion.test.ts +4 -1
- package/src/__tests__/swarm-session-integration.test.ts +24 -14
- package/src/__tests__/swarm-tool.test.ts +4 -2
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
- package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
- package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +1 -2
- package/src/__tests__/trust-store.test.ts +8 -83
- package/src/__tests__/twilio-config.test.ts +0 -1
- package/src/__tests__/twilio-provider.test.ts +0 -5
- package/src/__tests__/twilio-routes.test.ts +2 -3
- package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-quality.test.ts +2 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/agent/loop.ts +17 -1
- package/src/bundler/app-bundler.ts +40 -24
- package/src/calls/call-controller.ts +16 -0
- package/src/calls/guardian-question-copy.ts +1 -1
- package/src/calls/relay-server.ts +29 -13
- package/src/calls/voice-control-protocol.ts +1 -0
- package/src/calls/voice-quality.ts +1 -1
- package/src/calls/voice-session-bridge.ts +9 -3
- package/src/channels/types.ts +16 -0
- package/src/cli/commands/bash.ts +173 -0
- package/src/cli/commands/doctor.ts +15 -57
- package/src/cli/commands/memory.ts +3 -5
- package/src/cli/commands/oauth/connections.ts +4 -2
- package/src/cli/commands/oauth/providers.ts +1 -13
- package/src/cli/commands/sessions.ts +1 -1
- package/src/cli/commands/usage.ts +359 -0
- package/src/cli/http-client.ts +22 -12
- package/src/cli/program.ts +4 -0
- package/src/cli/reference.ts +2 -0
- package/src/cli.ts +251 -181
- package/src/config/assistant-feature-flags.ts +0 -7
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +0 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +4 -3
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
- package/src/config/bundled-skills/messaging/SKILL.md +0 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
- package/src/config/bundled-skills/sequences/SKILL.md +0 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
- package/src/config/env.ts +13 -0
- package/src/config/feature-flag-registry.json +15 -39
- package/src/config/loader.ts +7 -135
- package/src/config/schema.ts +0 -6
- package/src/config/schemas/channels.ts +1 -0
- package/src/config/schemas/elevenlabs.ts +2 -2
- package/src/config/schemas/security.ts +1 -2
- package/src/config/skills.ts +1 -1
- package/src/contacts/contact-store.ts +21 -75
- package/src/contacts/contacts-write.ts +6 -6
- package/src/contacts/types.ts +2 -0
- package/src/context/token-estimator.ts +35 -2
- package/src/context/window-manager.ts +16 -2
- package/src/daemon/approved-devices-store.ts +0 -44
- package/src/daemon/classifier.ts +1 -1
- package/src/daemon/config-watcher.ts +35 -11
- package/src/daemon/context-overflow-reducer.ts +13 -2
- package/src/daemon/handlers/config-ingress.ts +25 -8
- package/src/daemon/handlers/config-model.ts +22 -16
- package/src/daemon/handlers/config-telegram.ts +18 -6
- package/src/daemon/handlers/dictation.ts +0 -429
- package/src/daemon/handlers/sessions.ts +4 -116
- package/src/daemon/handlers/skills.ts +2 -201
- package/src/daemon/lifecycle.ts +21 -20
- package/src/daemon/message-types/contacts.ts +2 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/sessions.ts +2 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +42 -5
- package/src/daemon/session-agent-loop-handlers.ts +1 -1
- package/src/daemon/session-agent-loop.ts +27 -79
- package/src/daemon/session-error.ts +5 -4
- package/src/daemon/session-process.ts +17 -10
- package/src/daemon/session-runtime-assembly.ts +50 -0
- package/src/daemon/session-slash.ts +34 -22
- package/src/daemon/session.ts +1 -0
- package/src/daemon/shutdown-handlers.ts +15 -0
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/guardrails.ts +1 -1
- package/src/email/service.ts +0 -5
- package/src/events/domain-events.ts +1 -0
- package/src/hooks/templates.ts +1 -1
- package/src/media/app-icon-generator.ts +4 -3
- package/src/media/avatar-router.ts +5 -4
- package/src/media/gemini-image-service.ts +5 -5
- package/src/memory/admin.ts +2 -2
- package/src/memory/app-git-service.ts +0 -7
- package/src/memory/canonical-guardian-store.ts +25 -3
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-title-service.ts +2 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.ts +46 -33
- package/src/memory/external-conversation-store.ts +0 -30
- package/src/memory/guardian-action-store.ts +0 -31
- package/src/memory/guardian-approvals.ts +1 -56
- package/src/memory/indexer.ts +4 -3
- package/src/memory/items-extractor.ts +1 -1
- package/src/memory/job-handlers/backfill.ts +5 -2
- package/src/memory/job-handlers/index-maintenance.ts +2 -2
- package/src/memory/job-handlers/media-processing.ts +2 -2
- package/src/memory/job-handlers/summarization.ts +1 -1
- package/src/memory/job-utils.ts +1 -2
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/llm-usage-store.ts +57 -11
- package/src/memory/media-store.ts +4 -535
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
- package/src/memory/migrations/110-channel-guardian.ts +0 -1
- package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
- package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
- package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/published-pages-store.ts +0 -83
- package/src/memory/qdrant-circuit-breaker.ts +0 -8
- package/src/memory/retriever.test.ts +19 -12
- package/src/memory/retriever.ts +1 -1
- package/src/memory/schema/contacts.ts +2 -2
- package/src/memory/schema/oauth.ts +0 -1
- package/src/memory/search/semantic.ts +1 -8
- package/src/memory/shared-app-links-store.ts +0 -15
- package/src/messaging/registry.ts +0 -5
- package/src/messaging/style-analyzer.ts +1 -1
- package/src/notifications/copy-composer.ts +5 -13
- package/src/notifications/decision-engine.ts +2 -2
- package/src/notifications/deliveries-store.ts +0 -39
- package/src/notifications/guardian-question-mode.ts +6 -10
- package/src/notifications/preference-extractor.ts +1 -1
- package/src/oauth/byo-connection.test.ts +29 -20
- package/src/oauth/connect-orchestrator.ts +5 -3
- package/src/oauth/connect-types.ts +9 -2
- package/src/oauth/manual-token-connection.ts +9 -7
- package/src/oauth/oauth-store.ts +2 -8
- package/src/oauth/provider-behaviors.ts +11 -1
- package/src/oauth/seed-providers.ts +13 -5
- package/src/permissions/checker.ts +21 -2
- package/src/permissions/shell-identity.ts +0 -5
- package/src/permissions/trust-store.ts +0 -37
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
- package/src/prompts/system-prompt.ts +5 -14
- package/src/prompts/templates/BOOTSTRAP.md +1 -3
- package/src/providers/anthropic/client.ts +16 -8
- package/src/providers/managed-proxy/constants.ts +9 -11
- package/src/providers/managed-proxy/context.ts +14 -9
- package/src/providers/provider-send-message.ts +4 -52
- package/src/providers/registry.ts +29 -57
- package/src/providers/types.ts +1 -1
- package/src/runtime/actor-token-store.ts +0 -23
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +12 -6
- package/src/runtime/channel-retry-sweep.ts +6 -0
- package/src/runtime/http-router.ts +5 -1
- package/src/runtime/http-server.ts +101 -4
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/invite-instruction-generator.ts +25 -51
- package/src/runtime/invite-service.ts +0 -20
- package/src/runtime/middleware/error-handler.ts +1 -2
- package/src/runtime/routes/app-management-routes.ts +1 -0
- package/src/runtime/routes/attachment-routes.ts +1 -1
- package/src/runtime/routes/brain-graph-routes.ts +1 -1
- package/src/runtime/routes/btw-routes.ts +20 -1
- package/src/runtime/routes/call-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +64 -24
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +2 -2
- package/src/runtime/routes/documents-routes.ts +3 -3
- package/src/runtime/routes/global-search-routes.ts +1 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
- package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
- package/src/runtime/routes/inbound-message-handler.ts +10 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
- package/src/runtime/routes/integrations/slack/share.ts +5 -5
- package/src/runtime/routes/log-export-routes.ts +122 -10
- package/src/runtime/routes/secret-routes.ts +4 -4
- package/src/runtime/routes/session-query-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +53 -0
- package/src/runtime/routes/trust-rules-routes.ts +1 -1
- package/src/runtime/routes/workspace-routes.ts +3 -0
- package/src/runtime/verification-templates.ts +1 -1
- package/src/security/credential-backend.ts +148 -0
- package/src/security/oauth2.ts +5 -5
- package/src/security/secret-allowlist.ts +1 -1
- package/src/security/secure-keys.ts +98 -160
- package/src/security/token-manager.ts +0 -7
- package/src/sequence/guardrails.ts +0 -4
- package/src/sequence/store.ts +1 -20
- package/src/sequence/types.ts +1 -36
- package/src/signals/bash.ts +157 -0
- package/src/signals/cancel.ts +69 -0
- package/src/signals/conversation-undo.ts +127 -0
- package/src/signals/trust-rule.ts +174 -0
- package/src/skills/clawhub.ts +5 -5
- package/src/skills/managed-store.ts +4 -4
- package/src/skills/skillssh-registry.ts +6 -1
- package/src/swarm/backend-claude-code.ts +6 -6
- package/src/swarm/worker-backend.ts +1 -1
- package/src/swarm/worker-runner.ts +1 -1
- package/src/telegram/bot-username.ts +11 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
- package/src/telemetry/usage-telemetry-reporter.ts +181 -0
- package/src/tools/claude-code/claude-code.ts +6 -6
- package/src/tools/credentials/broker.ts +7 -5
- package/src/tools/credentials/vault.ts +11 -6
- package/src/tools/memory/handlers.test.ts +24 -26
- package/src/tools/memory/handlers.ts +1 -13
- package/src/tools/network/__tests__/web-search.test.ts +18 -86
- package/src/tools/network/web-search.ts +9 -15
- package/src/tools/registry.ts +5 -100
- package/src/tools/terminal/parser.ts +34 -4
- package/src/tools/tool-manifest.ts +0 -10
- package/src/usage/actors.ts +0 -12
- package/src/util/canonicalize-identity.ts +0 -9
- package/src/util/errors.ts +0 -3
- package/src/util/platform.ts +31 -8
- package/src/util/pricing.ts +0 -39
- package/src/watcher/constants.ts +0 -7
- package/src/watcher/providers/linear.ts +1 -1
- package/src/work-items/work-item-store.ts +4 -4
- package/src/workspace/commit-message-provider.ts +1 -1
- package/src/workspace/git-service.ts +44 -1
- package/src/workspace/provider-commit-message-generator.ts +11 -7
- package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
- package/src/browser-extension-relay/client.ts +0 -155
- package/src/contacts/index.ts +0 -18
- package/src/daemon/tls-certs.ts +0 -270
- package/src/errors.ts +0 -41
- package/src/events/index.ts +0 -18
- package/src/followups/index.ts +0 -10
- package/src/playbooks/index.ts +0 -10
- package/src/runtime/auth/index.ts +0 -44
- package/src/tasks/candidate-store.ts +0 -95
- package/src/tools/browser/api-map.ts +0 -313
- package/src/tools/browser/auto-navigate.ts +0 -469
- package/src/tools/browser/headless-browser.ts +0 -590
- package/src/tools/browser/recording-store.ts +0 -75
- package/src/tools/computer-use/registry.ts +0 -21
- package/src/tools/tasks/index.ts +0 -27
|
@@ -133,7 +133,7 @@ const log = getLogger("session-agent-loop");
|
|
|
133
133
|
*
|
|
134
134
|
* Returns the actual token count or null if it cannot be parsed.
|
|
135
135
|
*/
|
|
136
|
-
function parseActualTokensFromError(
|
|
136
|
+
export function parseActualTokensFromError(
|
|
137
137
|
errorMessage: string | null,
|
|
138
138
|
): number | null {
|
|
139
139
|
if (!errorMessage) return null;
|
|
@@ -710,10 +710,11 @@ export async function runAgentLoopImpl(
|
|
|
710
710
|
const preflightBudget = Math.floor(providerMaxTokens * (1 - safetyMargin));
|
|
711
711
|
let reducerState: ReducerState | undefined;
|
|
712
712
|
|
|
713
|
+
const toolTokenBudget = ctx.agentLoop.getToolTokenBudget(runMessages);
|
|
713
714
|
const preflightTokens = estimatePromptTokens(
|
|
714
715
|
runMessages,
|
|
715
716
|
ctx.systemPrompt,
|
|
716
|
-
{ providerName: ctx.provider.name },
|
|
717
|
+
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
717
718
|
);
|
|
718
719
|
|
|
719
720
|
if (overflowRecovery.enabled && preflightTokens > preflightBudget) {
|
|
@@ -747,6 +748,7 @@ export async function runAgentLoopImpl(
|
|
|
747
748
|
systemPrompt: ctx.systemPrompt,
|
|
748
749
|
contextWindow: config.contextWindow,
|
|
749
750
|
targetTokens: preflightBudget,
|
|
751
|
+
toolTokenBudget,
|
|
750
752
|
},
|
|
751
753
|
reducerState,
|
|
752
754
|
(msgs, signal, opts) =>
|
|
@@ -863,7 +865,7 @@ export async function runAgentLoopImpl(
|
|
|
863
865
|
const estimated = estimatePromptTokens(
|
|
864
866
|
checkpoint.history,
|
|
865
867
|
ctx.systemPrompt,
|
|
866
|
-
{ providerName: ctx.provider.name },
|
|
868
|
+
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
867
869
|
);
|
|
868
870
|
if (estimated > midLoopThreshold) {
|
|
869
871
|
rlog.warn(
|
|
@@ -993,6 +995,23 @@ export async function runAgentLoopImpl(
|
|
|
993
995
|
);
|
|
994
996
|
}
|
|
995
997
|
|
|
998
|
+
// If mid-loop compaction exhausted all attempts but the agent loop
|
|
999
|
+
// still yielded (yieldedForBudget is true), the turn is incomplete.
|
|
1000
|
+
// Escalate to the convergence loop's more aggressive reducer tiers
|
|
1001
|
+
// (tool-result truncation, media stubbing, injection downgrade)
|
|
1002
|
+
// instead of silently treating an incomplete turn as done.
|
|
1003
|
+
if (yieldedForBudget && !abortController.signal.aborted) {
|
|
1004
|
+
rlog.warn(
|
|
1005
|
+
{
|
|
1006
|
+
phase: "mid-loop-compact",
|
|
1007
|
+
midLoopCompactAttempts,
|
|
1008
|
+
maxAttempts: overflowRecovery.maxAttempts,
|
|
1009
|
+
},
|
|
1010
|
+
"Mid-loop compaction exhausted all attempts — escalating to convergence loop",
|
|
1011
|
+
);
|
|
1012
|
+
state.contextTooLargeDetected = true;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
996
1015
|
// One-shot ordering error retry
|
|
997
1016
|
if (
|
|
998
1017
|
state.orderingErrorDetected &&
|
|
@@ -1045,6 +1064,7 @@ export async function runAgentLoopImpl(
|
|
|
1045
1064
|
),
|
|
1046
1065
|
});
|
|
1047
1066
|
preRepairMessages = updatedHistory;
|
|
1067
|
+
preRunHistoryLength = updatedHistory.length;
|
|
1048
1068
|
}
|
|
1049
1069
|
if (!reducerState) {
|
|
1050
1070
|
reducerState = createInitialReducerState();
|
|
@@ -1061,7 +1081,7 @@ export async function runAgentLoopImpl(
|
|
|
1061
1081
|
const estimatedTokensAtOverflow = estimatePromptTokens(
|
|
1062
1082
|
ctx.messages,
|
|
1063
1083
|
ctx.systemPrompt,
|
|
1064
|
-
{ providerName: ctx.provider.name },
|
|
1084
|
+
{ providerName: ctx.provider.name, toolTokenBudget },
|
|
1065
1085
|
);
|
|
1066
1086
|
let correctedTarget = preflightBudget;
|
|
1067
1087
|
if (actualTokens && estimatedTokensAtOverflow > 0) {
|
|
@@ -1113,6 +1133,7 @@ export async function runAgentLoopImpl(
|
|
|
1113
1133
|
systemPrompt: ctx.systemPrompt,
|
|
1114
1134
|
contextWindow: config.contextWindow,
|
|
1115
1135
|
targetTokens: correctedTarget,
|
|
1136
|
+
toolTokenBudget,
|
|
1116
1137
|
},
|
|
1117
1138
|
reducerState,
|
|
1118
1139
|
(msgs, signal, opts) =>
|
|
@@ -1124,12 +1145,6 @@ export async function runAgentLoopImpl(
|
|
|
1124
1145
|
ctx.messages = step.messages;
|
|
1125
1146
|
currentInjectionMode = step.state.injectionMode;
|
|
1126
1147
|
|
|
1127
|
-
// If the reducer is now exhausted without compacting, break out
|
|
1128
|
-
// so the overflow policy path can attempt emergency compaction.
|
|
1129
|
-
if (reducerState.exhausted && !step.compactionResult?.compacted) {
|
|
1130
|
-
break;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
1148
|
if (step.compactionResult?.compacted) {
|
|
1134
1149
|
ctx.contextCompactedMessageCount +=
|
|
1135
1150
|
step.compactionResult.compactedPersistedMessages;
|
|
@@ -1183,77 +1198,10 @@ export async function runAgentLoopImpl(
|
|
|
1183
1198
|
);
|
|
1184
1199
|
}
|
|
1185
1200
|
|
|
1186
|
-
// When all reducer tiers are exhausted but the context is still too
|
|
1187
|
-
// large, attempt one last emergency compaction before consulting the
|
|
1188
|
-
// overflow policy. This covers the case where progress was made
|
|
1189
|
-
// (messages grew) and the normal tiers couldn't compact enough.
|
|
1190
|
-
if (state.contextTooLargeDetected && reducerState.exhausted) {
|
|
1191
|
-
const emergencyCompact = await ctx.contextWindowManager.maybeCompact(
|
|
1192
|
-
ctx.messages,
|
|
1193
|
-
abortController.signal,
|
|
1194
|
-
{
|
|
1195
|
-
lastCompactedAt: ctx.contextCompactedAt ?? undefined,
|
|
1196
|
-
force: true,
|
|
1197
|
-
minKeepRecentUserTurns: 0,
|
|
1198
|
-
targetInputTokensOverride: correctedTarget,
|
|
1199
|
-
},
|
|
1200
|
-
);
|
|
1201
|
-
if (emergencyCompact.compacted) {
|
|
1202
|
-
ctx.messages = emergencyCompact.messages;
|
|
1203
|
-
ctx.contextCompactedMessageCount +=
|
|
1204
|
-
emergencyCompact.compactedPersistedMessages;
|
|
1205
|
-
ctx.contextCompactedAt = Date.now();
|
|
1206
|
-
updateConversationContextWindow(
|
|
1207
|
-
ctx.conversationId,
|
|
1208
|
-
emergencyCompact.summaryText,
|
|
1209
|
-
ctx.contextCompactedMessageCount,
|
|
1210
|
-
);
|
|
1211
|
-
onEvent({
|
|
1212
|
-
type: "context_compacted",
|
|
1213
|
-
previousEstimatedInputTokens:
|
|
1214
|
-
emergencyCompact.previousEstimatedInputTokens,
|
|
1215
|
-
estimatedInputTokens: emergencyCompact.estimatedInputTokens,
|
|
1216
|
-
maxInputTokens: emergencyCompact.maxInputTokens,
|
|
1217
|
-
thresholdTokens: emergencyCompact.thresholdTokens,
|
|
1218
|
-
compactedMessages: emergencyCompact.compactedMessages,
|
|
1219
|
-
summaryCalls: emergencyCompact.summaryCalls,
|
|
1220
|
-
summaryInputTokens: emergencyCompact.summaryInputTokens,
|
|
1221
|
-
summaryOutputTokens: emergencyCompact.summaryOutputTokens,
|
|
1222
|
-
summaryModel: emergencyCompact.summaryModel,
|
|
1223
|
-
});
|
|
1224
|
-
emitUsage(
|
|
1225
|
-
ctx,
|
|
1226
|
-
emergencyCompact.summaryInputTokens,
|
|
1227
|
-
emergencyCompact.summaryOutputTokens,
|
|
1228
|
-
emergencyCompact.summaryModel,
|
|
1229
|
-
onEvent,
|
|
1230
|
-
"context_compactor",
|
|
1231
|
-
reqId,
|
|
1232
|
-
emergencyCompact.summaryCacheCreationInputTokens ?? 0,
|
|
1233
|
-
emergencyCompact.summaryCacheReadInputTokens ?? 0,
|
|
1234
|
-
collapseRawResponses(emergencyCompact.summaryRawResponses),
|
|
1235
|
-
);
|
|
1236
|
-
|
|
1237
|
-
runMessages = applyRuntimeInjections(ctx.messages, {
|
|
1238
|
-
...injectionOpts,
|
|
1239
|
-
mode: currentInjectionMode,
|
|
1240
|
-
});
|
|
1241
|
-
preRepairMessages = runMessages;
|
|
1242
|
-
preRunHistoryLength = runMessages.length;
|
|
1243
|
-
state.contextTooLargeDetected = false;
|
|
1244
|
-
|
|
1245
|
-
updatedHistory = await ctx.agentLoop.run(
|
|
1246
|
-
runMessages,
|
|
1247
|
-
eventHandler,
|
|
1248
|
-
abortController.signal,
|
|
1249
|
-
reqId,
|
|
1250
|
-
onCheckpoint,
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
1201
|
// All reducer tiers exhausted but provider still rejects —
|
|
1256
1202
|
// consult the overflow policy for latest-turn compression.
|
|
1203
|
+
// Emergency compaction is deferred to the policy-gated paths below
|
|
1204
|
+
// so that `request_user_approval` sessions collect consent first.
|
|
1257
1205
|
if (state.contextTooLargeDetected) {
|
|
1258
1206
|
const action = resolveOverflowAction({
|
|
1259
1207
|
overflowRecovery,
|
|
@@ -218,7 +218,7 @@ function classifyCore(
|
|
|
218
218
|
return {
|
|
219
219
|
code: "PROVIDER_WEB_SEARCH",
|
|
220
220
|
userMessage:
|
|
221
|
-
"An internal error occurred with web search.
|
|
221
|
+
"An internal error occurred with web search. Please try again.",
|
|
222
222
|
retryable: true,
|
|
223
223
|
errorCategory: "web_search_ordering",
|
|
224
224
|
};
|
|
@@ -226,7 +226,7 @@ function classifyCore(
|
|
|
226
226
|
if (isOrderingError(message)) {
|
|
227
227
|
return {
|
|
228
228
|
code: "PROVIDER_ORDERING",
|
|
229
|
-
userMessage: "An internal error occurred.
|
|
229
|
+
userMessage: "An internal error occurred. Please try again.",
|
|
230
230
|
retryable: true,
|
|
231
231
|
errorCategory: "tool_ordering",
|
|
232
232
|
};
|
|
@@ -308,7 +308,8 @@ function classifyByMessage(
|
|
|
308
308
|
if (isWebSearchOrderingError(message)) {
|
|
309
309
|
return {
|
|
310
310
|
code: "PROVIDER_WEB_SEARCH",
|
|
311
|
-
userMessage:
|
|
311
|
+
userMessage:
|
|
312
|
+
"An internal error occurred with web search. Please try again.",
|
|
312
313
|
retryable: true,
|
|
313
314
|
errorCategory: "web_search_ordering",
|
|
314
315
|
};
|
|
@@ -318,7 +319,7 @@ function classifyByMessage(
|
|
|
318
319
|
if (isOrderingError(message)) {
|
|
319
320
|
return {
|
|
320
321
|
code: "PROVIDER_ORDERING",
|
|
321
|
-
userMessage: "An internal error occurred.
|
|
322
|
+
userMessage: "An internal error occurred. Please try again.",
|
|
322
323
|
retryable: true,
|
|
323
324
|
errorCategory: "tool_ordering",
|
|
324
325
|
};
|
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
TurnInterfaceContext,
|
|
16
16
|
} from "../channels/types.js";
|
|
17
17
|
import { parseChannelId, parseInterfaceId } from "../channels/types.js";
|
|
18
|
-
import { getConfig } from "../config/loader.js";
|
|
18
|
+
import { API_KEY_PROVIDERS, getConfig } from "../config/loader.js";
|
|
19
19
|
import { listPendingRequestsByConversationScope } from "../memory/canonical-guardian-store.js";
|
|
20
20
|
import {
|
|
21
21
|
addMessage,
|
|
@@ -27,6 +27,7 @@ import { extractPreferences } from "../notifications/preference-extractor.js";
|
|
|
27
27
|
import { createPreference } from "../notifications/preferences-store.js";
|
|
28
28
|
import type { Message } from "../providers/types.js";
|
|
29
29
|
import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
|
|
30
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
30
31
|
import { getLogger } from "../util/logger.js";
|
|
31
32
|
import type {
|
|
32
33
|
ServerMessage,
|
|
@@ -47,12 +48,15 @@ import { resolveVerificationSessionIntent } from "./verification-session-intent.
|
|
|
47
48
|
const log = getLogger("session-process");
|
|
48
49
|
|
|
49
50
|
/** Build a model_info event with fresh config data. */
|
|
50
|
-
export function buildModelInfoEvent(): ServerMessage {
|
|
51
|
+
export async function buildModelInfoEvent(): Promise<ServerMessage> {
|
|
51
52
|
const config = getConfig();
|
|
52
|
-
const configured =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const configured: string[] = ["ollama"];
|
|
54
|
+
for (const p of API_KEY_PROVIDERS) {
|
|
55
|
+
if (p === "ollama") continue;
|
|
56
|
+
if (await getSecureKeyAsync(p)) {
|
|
57
|
+
configured.push(p);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
56
60
|
return {
|
|
57
61
|
type: "model_info",
|
|
58
62
|
model: config.model,
|
|
@@ -296,7 +300,10 @@ export async function drainQueue(
|
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
// Resolve slash commands for queued messages
|
|
299
|
-
const slashResult = resolveSlash(
|
|
303
|
+
const slashResult = await resolveSlash(
|
|
304
|
+
next.content,
|
|
305
|
+
buildSlashContext(session),
|
|
306
|
+
);
|
|
300
307
|
|
|
301
308
|
// Unknown slash — persist the exchange and continue draining.
|
|
302
309
|
// Persist each message before pushing to session.messages so that a
|
|
@@ -365,7 +372,7 @@ export async function drainQueue(
|
|
|
365
372
|
isModelSlashCommand(next.content) ||
|
|
366
373
|
isProviderShortcut(next.content)
|
|
367
374
|
) {
|
|
368
|
-
next.onEvent(buildModelInfoEvent());
|
|
375
|
+
next.onEvent(await buildModelInfoEvent());
|
|
369
376
|
}
|
|
370
377
|
next.onEvent({ type: "assistant_text_delta", text: slashResult.message });
|
|
371
378
|
session.traceEmitter.emit(
|
|
@@ -651,7 +658,7 @@ export async function processMessage(
|
|
|
651
658
|
}
|
|
652
659
|
|
|
653
660
|
// Resolve slash commands before persistence
|
|
654
|
-
const slashResult = resolveSlash(content, buildSlashContext(session));
|
|
661
|
+
const slashResult = await resolveSlash(content, buildSlashContext(session));
|
|
655
662
|
|
|
656
663
|
// Unknown slash command — persist the exchange (user + assistant) so the
|
|
657
664
|
// messageId is real. Persist each message before pushing to session.messages
|
|
@@ -715,7 +722,7 @@ export async function processMessage(
|
|
|
715
722
|
// Emit fresh model info before the text delta so the client has
|
|
716
723
|
// up-to-date configuredProviders when rendering /model or /models UI.
|
|
717
724
|
if (isModelSlashCommand(content) || isProviderShortcut(content)) {
|
|
718
|
-
onEvent(buildModelInfoEvent());
|
|
725
|
+
onEvent(await buildModelInfoEvent());
|
|
719
726
|
}
|
|
720
727
|
onEvent({ type: "assistant_text_delta", text: slashResult.message });
|
|
721
728
|
session.traceEmitter.emit(
|
|
@@ -37,6 +37,8 @@ export interface ChannelCapabilities {
|
|
|
37
37
|
pttActivationKey?: string;
|
|
38
38
|
/** Whether the client has been granted microphone permission by the OS. */
|
|
39
39
|
microphonePermissionGranted?: boolean;
|
|
40
|
+
/** Chat type from the gateway (e.g. "private", "group", "supergroup", "channel", "im", "mpim"). */
|
|
41
|
+
chatType?: string;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -296,6 +298,7 @@ export function resolveChannelCapabilities(
|
|
|
296
298
|
sourceChannel?: string | null,
|
|
297
299
|
sourceInterface?: string | null,
|
|
298
300
|
pttMetadata?: PttMetadata | null,
|
|
301
|
+
chatType?: string | null,
|
|
299
302
|
): ChannelCapabilities {
|
|
300
303
|
// Normalise legacy pseudo-channel IDs to canonical ChannelId values.
|
|
301
304
|
let channel: string;
|
|
@@ -330,6 +333,8 @@ export function resolveChannelCapabilities(
|
|
|
330
333
|
}
|
|
331
334
|
}
|
|
332
335
|
|
|
336
|
+
const resolvedChatType = chatType ?? undefined;
|
|
337
|
+
|
|
333
338
|
switch (channel) {
|
|
334
339
|
case "vellum": {
|
|
335
340
|
const supportsDesktopUi = iface === "macos";
|
|
@@ -342,6 +347,7 @@ export function resolveChannelCapabilities(
|
|
|
342
347
|
pttMetadata?.pttActivationKey,
|
|
343
348
|
),
|
|
344
349
|
microphonePermissionGranted: pttMetadata?.microphonePermissionGranted,
|
|
350
|
+
chatType: resolvedChatType,
|
|
345
351
|
};
|
|
346
352
|
}
|
|
347
353
|
case "telegram":
|
|
@@ -354,6 +360,7 @@ export function resolveChannelCapabilities(
|
|
|
354
360
|
dashboardCapable: false,
|
|
355
361
|
supportsDynamicUi: false,
|
|
356
362
|
supportsVoiceInput: false,
|
|
363
|
+
chatType: resolvedChatType,
|
|
357
364
|
};
|
|
358
365
|
default:
|
|
359
366
|
return {
|
|
@@ -361,10 +368,28 @@ export function resolveChannelCapabilities(
|
|
|
361
368
|
dashboardCapable: false,
|
|
362
369
|
supportsDynamicUi: false,
|
|
363
370
|
supportsVoiceInput: false,
|
|
371
|
+
chatType: resolvedChatType,
|
|
364
372
|
};
|
|
365
373
|
}
|
|
366
374
|
}
|
|
367
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Returns true when the chat type indicates a group/multi-party conversation
|
|
378
|
+
* (Telegram group/supergroup, Slack channel/group/mpim, etc.).
|
|
379
|
+
*/
|
|
380
|
+
export function isGroupChatType(chatType?: string): boolean {
|
|
381
|
+
if (!chatType) return false;
|
|
382
|
+
switch (chatType) {
|
|
383
|
+
case "group":
|
|
384
|
+
case "supergroup":
|
|
385
|
+
case "channel":
|
|
386
|
+
case "mpim":
|
|
387
|
+
return true;
|
|
388
|
+
default:
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
368
393
|
/** Context about the active workspace surface, passed to applyRuntimeInjections. */
|
|
369
394
|
export interface ActiveSurfaceContext {
|
|
370
395
|
surfaceId: string;
|
|
@@ -632,6 +657,31 @@ export function injectChannelCapabilityContext(
|
|
|
632
657
|
}
|
|
633
658
|
}
|
|
634
659
|
|
|
660
|
+
// Inject group chat etiquette only when the chat type indicates a multi-party
|
|
661
|
+
// conversation, avoiding misconditioned "stay silent" guidance in 1:1 DMs.
|
|
662
|
+
if (isGroupChatType(caps.chatType)) {
|
|
663
|
+
lines.push(`chat_type: ${caps.chatType}`);
|
|
664
|
+
lines.push("");
|
|
665
|
+
lines.push("GROUP CHAT ETIQUETTE:");
|
|
666
|
+
lines.push(
|
|
667
|
+
"- You are a **participant**, not the user's proxy. Think before you speak.",
|
|
668
|
+
);
|
|
669
|
+
lines.push(
|
|
670
|
+
"- **Respond when:** directly mentioned, you can add genuine value, something witty fits naturally, or correcting important misinformation.",
|
|
671
|
+
);
|
|
672
|
+
lines.push(
|
|
673
|
+
'- **Stay silent when:** casual banter between humans, someone already answered, your response would just be "yeah" or "nice", or the conversation flows fine without you.',
|
|
674
|
+
);
|
|
675
|
+
lines.push(
|
|
676
|
+
"- **The human rule:** humans don't respond to every message in a group chat. Neither should you. Quality over quantity.",
|
|
677
|
+
);
|
|
678
|
+
if (caps.channel === "slack") {
|
|
679
|
+
lines.push(
|
|
680
|
+
"- Use emoji reactions naturally to acknowledge without cluttering.",
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
635
685
|
lines.push("</channel_capabilities>");
|
|
636
686
|
|
|
637
687
|
const block = lines.join("\n");
|
|
@@ -5,10 +5,16 @@ import { join } from "node:path";
|
|
|
5
5
|
import QRCode from "qrcode";
|
|
6
6
|
|
|
7
7
|
import { getGatewayPort, getIngressPublicBaseUrl } from "../config/env.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
API_KEY_PROVIDERS,
|
|
10
|
+
getConfig,
|
|
11
|
+
loadRawConfig,
|
|
12
|
+
saveRawConfig,
|
|
13
|
+
} from "../config/loader.js";
|
|
9
14
|
import { resolveSkillStates } from "../config/skill-state.js";
|
|
10
15
|
import { loadSkillCatalog } from "../config/skills.js";
|
|
11
16
|
import { initializeProviders } from "../providers/registry.js";
|
|
17
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
12
18
|
import {
|
|
13
19
|
buildInvocableSlashCatalog,
|
|
14
20
|
resolveSlashSkillCommand,
|
|
@@ -53,14 +59,12 @@ export interface SlashContext {
|
|
|
53
59
|
|
|
54
60
|
const AVAILABLE_MODELS = [
|
|
55
61
|
"claude-opus-4-6",
|
|
56
|
-
"claude-opus-4-6-fast",
|
|
57
62
|
"claude-sonnet-4-6",
|
|
58
63
|
"claude-haiku-4-5-20251001",
|
|
59
64
|
] as const;
|
|
60
65
|
|
|
61
66
|
const MODEL_DISPLAY_NAMES: Record<string, string> = {
|
|
62
67
|
"claude-opus-4-6": "Claude Opus 4.6",
|
|
63
|
-
"claude-opus-4-6-fast": "Claude Opus 4.6 Fast",
|
|
64
68
|
"claude-sonnet-4-6": "Claude Sonnet 4.6",
|
|
65
69
|
"claude-haiku-4-5-20251001": "Claude Haiku 4.5",
|
|
66
70
|
};
|
|
@@ -75,11 +79,6 @@ const PROVIDER_MODEL_SHORTCUTS: Record<
|
|
|
75
79
|
model: "claude-opus-4-6",
|
|
76
80
|
displayName: "Claude Opus 4.6",
|
|
77
81
|
},
|
|
78
|
-
"opus-fast": {
|
|
79
|
-
provider: "anthropic",
|
|
80
|
-
model: "claude-opus-4-6-fast",
|
|
81
|
-
displayName: "Claude Opus 4.6 Fast",
|
|
82
|
-
},
|
|
83
82
|
sonnet: {
|
|
84
83
|
provider: "anthropic",
|
|
85
84
|
model: "claude-sonnet-4-6",
|
|
@@ -146,7 +145,9 @@ function matchModel(input: string): string | undefined {
|
|
|
146
145
|
return AVAILABLE_MODELS.find((m) => m.includes(lower));
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
function resolveProviderModelCommand(
|
|
148
|
+
async function resolveProviderModelCommand(
|
|
149
|
+
content: string,
|
|
150
|
+
): Promise<SlashResolution | null> {
|
|
150
151
|
const trimmed = content.trim();
|
|
151
152
|
if (!trimmed.startsWith("/")) return null;
|
|
152
153
|
|
|
@@ -163,7 +164,7 @@ function resolveProviderModelCommand(content: string): SlashResolution | null {
|
|
|
163
164
|
const name = getAssistantName();
|
|
164
165
|
|
|
165
166
|
// Check if API key exists for this provider (Ollama doesn't require an API key)
|
|
166
|
-
if (provider !== "ollama" && !
|
|
167
|
+
if (provider !== "ollama" && !(await getSecureKeyAsync(provider))) {
|
|
167
168
|
return {
|
|
168
169
|
kind: "unknown",
|
|
169
170
|
message: `Cannot switch to ${displayName}. No API key configured for ${provider}.\n\nSet it with: \`keys set ${provider} <your-key>\``,
|
|
@@ -189,7 +190,7 @@ function resolveProviderModelCommand(content: string): SlashResolution | null {
|
|
|
189
190
|
|
|
190
191
|
// Re-initialize providers with new config
|
|
191
192
|
const newConfig = getConfig();
|
|
192
|
-
initializeProviders(newConfig);
|
|
193
|
+
await initializeProviders(newConfig);
|
|
193
194
|
|
|
194
195
|
const switchedMsg = name
|
|
195
196
|
? `Switched ${name} to **${displayName}**. New conversations will use this model.`
|
|
@@ -201,14 +202,23 @@ function resolveProviderModelCommand(content: string): SlashResolution | null {
|
|
|
201
202
|
};
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
function resolveModelList(): SlashResolution {
|
|
205
|
+
async function resolveModelList(): Promise<SlashResolution> {
|
|
205
206
|
const config = getConfig();
|
|
207
|
+
|
|
208
|
+
// Build a set of providers that have a configured API key.
|
|
209
|
+
const configuredProviders = new Set<string>(["ollama"]);
|
|
210
|
+
for (const p of API_KEY_PROVIDERS) {
|
|
211
|
+
if (await getSecureKeyAsync(p)) {
|
|
212
|
+
configuredProviders.add(p);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
206
216
|
const lines = ["Available models:\n"];
|
|
207
217
|
|
|
208
218
|
for (const [cmd, { provider, model, displayName }] of Object.entries(
|
|
209
219
|
PROVIDER_MODEL_SHORTCUTS,
|
|
210
220
|
)) {
|
|
211
|
-
const hasKey =
|
|
221
|
+
const hasKey = configuredProviders.has(provider);
|
|
212
222
|
const isCurrent = config.provider === provider && config.model === model;
|
|
213
223
|
const status = hasKey ? "✓" : "✗";
|
|
214
224
|
const current = isCurrent ? " **[current]**" : "";
|
|
@@ -224,11 +234,13 @@ function resolveModelList(): SlashResolution {
|
|
|
224
234
|
};
|
|
225
235
|
}
|
|
226
236
|
|
|
227
|
-
function resolveModelCommand(
|
|
237
|
+
async function resolveModelCommand(
|
|
238
|
+
content: string,
|
|
239
|
+
): Promise<SlashResolution | null> {
|
|
228
240
|
const trimmed = content.trim();
|
|
229
241
|
// Match /models → route to list
|
|
230
242
|
if (trimmed === "/models") {
|
|
231
|
-
return resolveModelList();
|
|
243
|
+
return await resolveModelList();
|
|
232
244
|
}
|
|
233
245
|
|
|
234
246
|
if (!trimmed.startsWith("/model")) return null;
|
|
@@ -251,7 +263,7 @@ function resolveModelCommand(content: string): SlashResolution | null {
|
|
|
251
263
|
|
|
252
264
|
// Handle /model list
|
|
253
265
|
if (args === "list") {
|
|
254
|
-
return resolveModelList();
|
|
266
|
+
return await resolveModelList();
|
|
255
267
|
}
|
|
256
268
|
|
|
257
269
|
// Try to match the model name
|
|
@@ -280,7 +292,7 @@ function resolveModelCommand(content: string): SlashResolution | null {
|
|
|
280
292
|
}
|
|
281
293
|
|
|
282
294
|
// Validate that Anthropic provider is available
|
|
283
|
-
if (!
|
|
295
|
+
if (!(await getSecureKeyAsync("anthropic"))) {
|
|
284
296
|
const displayName = MODEL_DISPLAY_NAMES[matched] ?? matched;
|
|
285
297
|
return {
|
|
286
298
|
kind: "unknown",
|
|
@@ -294,7 +306,7 @@ function resolveModelCommand(content: string): SlashResolution | null {
|
|
|
294
306
|
raw.model = matched;
|
|
295
307
|
saveRawConfig(raw);
|
|
296
308
|
const config = getConfig();
|
|
297
|
-
initializeProviders(config);
|
|
309
|
+
await initializeProviders(config);
|
|
298
310
|
|
|
299
311
|
const displayName = MODEL_DISPLAY_NAMES[matched] ?? matched;
|
|
300
312
|
const switchedMsg = name
|
|
@@ -343,16 +355,16 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
|
|
|
343
355
|
* Resolve slash commands against the current skill catalog.
|
|
344
356
|
* Returns `unknown` with a deterministic message, or the (possibly rewritten) content.
|
|
345
357
|
*/
|
|
346
|
-
export function resolveSlash(
|
|
358
|
+
export async function resolveSlash(
|
|
347
359
|
content: string,
|
|
348
360
|
context?: SlashContext,
|
|
349
|
-
): SlashResolution {
|
|
361
|
+
): Promise<SlashResolution> {
|
|
350
362
|
// Check provider shortcuts first (/gpt4, /opus, etc.)
|
|
351
|
-
const providerResult = resolveProviderModelCommand(content);
|
|
363
|
+
const providerResult = await resolveProviderModelCommand(content);
|
|
352
364
|
if (providerResult) return providerResult;
|
|
353
365
|
|
|
354
366
|
// Handle /model command
|
|
355
|
-
const modelResult = resolveModelCommand(content);
|
|
367
|
+
const modelResult = await resolveModelCommand(content);
|
|
356
368
|
if (modelResult) return modelResult;
|
|
357
369
|
|
|
358
370
|
// Handle /pair command
|
package/src/daemon/session.ts
CHANGED
|
@@ -348,6 +348,7 @@ export class Session {
|
|
|
348
348
|
provider,
|
|
349
349
|
systemPrompt: () => resolveSystemPromptCallback([]).systemPrompt,
|
|
350
350
|
config: config.contextWindow,
|
|
351
|
+
toolTokenBudget: this.agentLoop.getToolTokenBudget(),
|
|
351
352
|
});
|
|
352
353
|
|
|
353
354
|
void getHookManager().trigger("session-start", {
|
|
@@ -24,6 +24,7 @@ export interface ShutdownDeps {
|
|
|
24
24
|
memoryWorker: { stop(): void };
|
|
25
25
|
qdrantManager: QdrantManager;
|
|
26
26
|
mcpManager: McpServerManager | null;
|
|
27
|
+
telemetryReporter: { stop(): Promise<void> } | null;
|
|
27
28
|
cleanupPidFile: () => void;
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -87,6 +88,20 @@ export function installShutdownHandlers(deps: ShutdownDeps): void {
|
|
|
87
88
|
log.warn({ err }, "Enrichment service shutdown failed (non-fatal)");
|
|
88
89
|
}
|
|
89
90
|
|
|
91
|
+
if (deps.telemetryReporter) {
|
|
92
|
+
try {
|
|
93
|
+
const timeout = new Promise<void>((_, reject) =>
|
|
94
|
+
setTimeout(
|
|
95
|
+
() => reject(new Error("Telemetry flush timed out")),
|
|
96
|
+
3_000,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
await Promise.race([deps.telemetryReporter.stop(), timeout]);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
log.warn({ err }, "Telemetry reporter shutdown failed (non-fatal)");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
if (deps.runtimeHttp) await deps.runtimeHttp.stop();
|
|
91
106
|
await browserManager.closeAllPages();
|
|
92
107
|
deps.scheduler.stop();
|
|
@@ -107,7 +107,7 @@ export async function handleWatchObservation(
|
|
|
107
107
|
|
|
108
108
|
async function generateCommentary(session: WatchSession): Promise<void> {
|
|
109
109
|
try {
|
|
110
|
-
const provider = getConfiguredProvider();
|
|
110
|
+
const provider = await getConfiguredProvider();
|
|
111
111
|
if (!provider) {
|
|
112
112
|
log.warn(
|
|
113
113
|
{ watchId: session.watchId },
|
|
@@ -198,7 +198,7 @@ export async function generateSummary(session: WatchSession): Promise<void> {
|
|
|
198
198
|
},
|
|
199
199
|
"generateSummary starting — calling LLM",
|
|
200
200
|
);
|
|
201
|
-
const provider = getConfiguredProvider();
|
|
201
|
+
const provider = await getConfiguredProvider();
|
|
202
202
|
if (!provider) {
|
|
203
203
|
log.warn(
|
|
204
204
|
{ watchId: session.watchId },
|
package/src/email/guardrails.ts
CHANGED
|
@@ -104,7 +104,7 @@ export function setDailySendCap(cap: number): void {
|
|
|
104
104
|
saveState(state);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
function isAddressAllowed(email: string): {
|
|
108
108
|
allowed: boolean;
|
|
109
109
|
reason?: string;
|
|
110
110
|
rule?: AddressRule;
|
package/src/email/service.ts
CHANGED
package/src/hooks/templates.ts
CHANGED
|
@@ -18,7 +18,7 @@ const log = getLogger("hooks-templates");
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Install bundled hook templates into the user's hooks directory.
|
|
21
|
-
* Templates are copied from `assistant/hook-templates/` to `~/.vellum/
|
|
21
|
+
* Templates are copied from `assistant/hook-templates/` to `~/.vellum/hooks/`.
|
|
22
22
|
* - Never overwrites existing hooks (user modifications are preserved).
|
|
23
23
|
* - Newly installed hooks are disabled by default.
|
|
24
24
|
*/
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
buildManagedBaseUrl,
|
|
16
16
|
resolveManagedProxyContext,
|
|
17
17
|
} from "../providers/managed-proxy/context.js";
|
|
18
|
+
import { getSecureKeyAsync } from "../security/secure-keys.js";
|
|
18
19
|
import { getLogger } from "../util/logger.js";
|
|
19
20
|
import {
|
|
20
21
|
generateImage,
|
|
@@ -36,15 +37,15 @@ export async function generateAppIcon(
|
|
|
36
37
|
appDescription?: string,
|
|
37
38
|
): Promise<void> {
|
|
38
39
|
const config = getConfig();
|
|
39
|
-
const apiKey =
|
|
40
|
+
const apiKey = await getSecureKeyAsync("gemini");
|
|
40
41
|
|
|
41
42
|
let credentials: ImageGenCredentials | undefined;
|
|
42
43
|
if (apiKey) {
|
|
43
44
|
credentials = { type: "direct", apiKey };
|
|
44
45
|
} else {
|
|
45
|
-
const managedBaseUrl = buildManagedBaseUrl("vertex");
|
|
46
|
+
const managedBaseUrl = await buildManagedBaseUrl("vertex");
|
|
46
47
|
if (managedBaseUrl) {
|
|
47
|
-
const ctx = resolveManagedProxyContext();
|
|
48
|
+
const ctx = await resolveManagedProxyContext();
|
|
48
49
|
credentials = {
|
|
49
50
|
type: "managed-proxy",
|
|
50
51
|
assistantApiKey: ctx.assistantApiKey,
|