@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e
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 +1 -1
- package/Dockerfile +8 -0
- package/docs/activation-funnel-telemetry.md +13 -7
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/openapi.yaml +530 -107
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +8 -0
- package/src/__tests__/activation-early-marking.test.ts +6 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
- package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
- package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
- package/src/__tests__/app-bundler.test.ts +4 -12
- package/src/__tests__/app-executors.test.ts +2 -49
- package/src/__tests__/app-routes-csp.test.ts +114 -146
- package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
- package/src/__tests__/build-persisted-content.test.ts +96 -0
- package/src/__tests__/bundle-scanner.test.ts +27 -1
- package/src/__tests__/call-controller.test.ts +291 -0
- package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
- package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
- package/src/__tests__/config-loader-backfill.test.ts +9 -4
- package/src/__tests__/config-schema-cmd.test.ts +10 -11
- package/src/__tests__/config-schema.test.ts +190 -257
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
- package/src/__tests__/conversation-agent-loop.test.ts +22 -9
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
- package/src/__tests__/conversation-process-callsite.test.ts +12 -19
- package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
- package/src/__tests__/conversation-summarize-route.test.ts +36 -44
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
- package/src/__tests__/conversation-usage.test.ts +4 -12
- package/src/__tests__/credential-routes.test.ts +250 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/db-migration-rollback.test.ts +22 -0
- package/src/__tests__/empty-response-hook.test.ts +186 -1
- package/src/__tests__/external-plugin-loader.test.ts +11 -5
- package/src/__tests__/heartbeat-service.test.ts +0 -28
- package/src/__tests__/host-shell-tool.test.ts +2 -0
- package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
- package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -16
- package/src/__tests__/list-all-apps.test.ts +0 -4
- package/src/__tests__/llm-context-resolution.test.ts +32 -56
- package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
- package/src/__tests__/llm-resolver.test.ts +342 -602
- package/src/__tests__/llm-schema.test.ts +79 -37
- package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
- package/src/__tests__/media-stream-output.test.ts +259 -3
- package/src/__tests__/media-stream-server-integration.test.ts +22 -1
- package/src/__tests__/media-stream-stt-session.test.ts +47 -0
- package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
- package/src/__tests__/memory-recall-log-store.test.ts +47 -13
- package/src/__tests__/mock-gateway-ipc.ts +46 -1
- package/src/__tests__/mtime-cache.test.ts +61 -0
- package/src/__tests__/navigate-settings-tab.test.ts +2 -0
- package/src/__tests__/normalize-onboarding.test.ts +33 -0
- package/src/__tests__/onboarding-persona-write.test.ts +26 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
- package/src/__tests__/provider-connections-backfill.test.ts +138 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
- package/src/__tests__/provider-registry-ollama.test.ts +8 -18
- package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
- package/src/__tests__/provider-usage-tracking.test.ts +9 -19
- package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
- package/src/__tests__/published-app-updater.test.ts +22 -16
- package/src/__tests__/registry.test.ts +5 -16
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
- package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
- package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
- package/src/__tests__/reveal-success-registry.test.ts +123 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
- package/src/__tests__/secret-fixtures.ts +9 -0
- package/src/__tests__/server-history-render.test.ts +28 -0
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/slack-share-routes.test.ts +0 -1
- package/src/__tests__/stt-stream-session.test.ts +6 -5
- package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
- package/src/__tests__/subagent-disposal.test.ts +2 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +37 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
- package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
- package/src/__tests__/telemetry-routes.test.ts +99 -19
- package/src/__tests__/tool-audit.test.ts +34 -4
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-profiler.test.ts +72 -1
- package/src/__tests__/tool-result-spool.test.ts +49 -4
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
- package/src/__tests__/ui-channel-variants.test.ts +108 -0
- package/src/__tests__/ui-shape-teaching.test.ts +255 -0
- package/src/__tests__/usage-attribution.test.ts +18 -41
- package/src/__tests__/user-plugin-loader.test.ts +4 -4
- package/src/__tests__/voice-config-update.test.ts +46 -0
- package/src/__tests__/voice-session-bridge.test.ts +216 -84
- package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
- package/src/agent/loop.ts +4 -3
- package/src/api/events/open-conversation.test.ts +64 -0
- package/src/api/events/open-conversation.ts +33 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +25 -35
- package/src/bundler/app-bundler.ts +34 -48
- package/src/bundler/app-compiler.ts +39 -4
- package/src/bundler/bundle-scanner.ts +13 -0
- package/src/bundler/manifest.ts +1 -1
- package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
- package/src/calls/call-constants.ts +5 -0
- package/src/calls/call-controller.ts +100 -32
- package/src/calls/call-transport.ts +9 -0
- package/src/calls/media-stream-output.ts +107 -4
- package/src/calls/media-stream-server.ts +29 -9
- package/src/calls/media-stream-stt-session.ts +7 -1
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/calls/voice-session-bridge.ts +84 -63
- package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
- package/src/cli/commands/__tests__/notifications.test.ts +24 -3
- package/src/cli/commands/config.help.ts +6 -6
- package/src/cli/commands/credentials.help.ts +13 -0
- package/src/cli/commands/credentials.ts +6 -1
- package/src/cli/commands/email.help.ts +7 -0
- package/src/cli/commands/email.ts +35 -1
- package/src/cli/commands/inference-providers.ts +168 -107
- package/src/cli/commands/inference.help.ts +118 -46
- package/src/cli/commands/memory/index.help.ts +23 -0
- package/src/cli/commands/memory/nodes.ts +146 -0
- package/src/cli/commands/notifications.help.ts +10 -10
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
- package/src/cli/commands/oauth/connect.test.ts +126 -0
- package/src/cli/commands/oauth/connect.ts +29 -6
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/status.test.ts +69 -3
- package/src/cli/commands/oauth/status.ts +50 -12
- package/src/cli/commands/plugins.help.ts +13 -2
- package/src/cli/commands/plugins.ts +61 -7
- package/src/cli/commands/telemetry.help.ts +13 -0
- package/src/cli/commands/telemetry.ts +45 -5
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
- package/src/cli/lib/bundled-marketplace.json +93 -36
- package/src/cli/lib/inspect-plugin.ts +5 -1
- package/src/cli/lib/install-from-github.ts +68 -4
- package/src/cli/lib/plugin-catalog-local.ts +6 -2
- package/src/cli/lib/plugin-fingerprint.ts +3 -3
- package/src/cli/lib/upgrade-plugin.ts +236 -35
- package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
- package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
- package/src/config/__tests__/profile-materialization.test.ts +1 -88
- package/src/config/bundled-skills/AGENTS.md +3 -30
- package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
- package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
- package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/call-site-defaults.ts +3 -3
- package/src/config/feature-flag-registry.json +24 -39
- package/src/config/llm-resolver.ts +88 -475
- package/src/config/profile-materialization.ts +11 -11
- package/src/config/schema.ts +93 -127
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/__tests__/stt.test.ts +31 -3
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/llm.ts +52 -15
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/config/schemas/memory-v2.ts +2 -2
- package/src/config/schemas/memory-v3.ts +1 -1
- package/src/config/schemas/services.ts +6 -3
- package/src/config/schemas/stt.ts +38 -21
- package/src/config/schemas/tts.ts +13 -18
- package/src/config/skills.ts +31 -21
- package/src/context/compactor.ts +44 -7
- package/src/context/post-turn-tool-result-truncation.ts +4 -2
- package/src/context/tool-result-spool.ts +26 -5
- package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
- package/src/conversations/message-consolidation.ts +22 -2
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
- package/src/daemon/app-source-watcher.ts +17 -23
- package/src/daemon/chat-credential-redaction.ts +1365 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
- package/src/daemon/conversation-agent-loop.ts +71 -31
- package/src/daemon/conversation-error.ts +36 -14
- package/src/daemon/conversation-process.ts +22 -0
- package/src/daemon/conversation-store.ts +35 -0
- package/src/daemon/conversation-surfaces.ts +25 -25
- package/src/daemon/conversation-tool-setup.ts +33 -7
- package/src/daemon/conversation.ts +55 -5
- package/src/daemon/handlers/shared.ts +33 -2
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/message-types/conversations.ts +6 -17
- package/src/daemon/providers-setup.ts +8 -0
- package/src/daemon/tool-setup-types.ts +7 -1
- package/src/daemon/wake-conversation-ops.ts +10 -1
- package/src/hooks/types.ts +9 -0
- package/src/ipc/__tests__/email-ipc.test.ts +90 -0
- package/src/ipc/gateway-client.test.ts +59 -0
- package/src/ipc/gateway-client.ts +70 -27
- package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/live-voice-session.ts +443 -40
- package/src/live-voice/protocol.ts +143 -1
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
- package/src/monitoring/plugin-source-watch.ts +3 -62
- package/src/notifications/README.md +1 -1
- package/src/permissions/checker.ts +8 -4
- package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
- package/src/persistence/conversation-crud.ts +104 -2
- package/src/persistence/db-init.ts +14 -3
- package/src/persistence/job-handlers/cleanup.ts +15 -7
- package/src/persistence/llm-request-log-store.ts +88 -52
- package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
- package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
- package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
- package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
- package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
- package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
- package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
- package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
- package/src/persistence/migrations/helpers/relocation.ts +44 -1
- package/src/persistence/migrations/run-migrations.ts +25 -1
- package/src/persistence/schema/infrastructure.ts +9 -0
- package/src/persistence/schema/memory-core.ts +3 -0
- package/src/persistence/schema/memory-injection.ts +2 -0
- package/src/persistence/steps.ts +36 -0
- package/src/platform/client.test.ts +1 -44
- package/src/platform/client.ts +10 -20
- package/src/platform/consent-cache.test.ts +89 -23
- package/src/platform/consent-cache.ts +71 -33
- package/src/plugin-api/constants.ts +12 -0
- package/src/plugin-api/conversation-turn.ts +37 -14
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/resolve-credential.ts +75 -0
- package/src/plugin-api/vision-support.test.ts +8 -19
- package/src/plugin-api/vision-support.ts +26 -17
- package/src/plugins/collect-source-versions.ts +77 -0
- package/src/plugins/defaults/compaction/compact.ts +6 -0
- package/src/plugins/defaults/compaction/window-manager.ts +9 -0
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
- package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
- package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
- package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
- package/src/plugins/defaults/index.ts +8 -1
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
- package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
- package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
- package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
- package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
- package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
- package/src/plugins/defaults/memory/graph/store.ts +114 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
- package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
- package/src/plugins/defaults/memory/memory-db.ts +3 -2
- package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
- package/src/plugins/defaults/memory/path-containment.ts +21 -0
- package/src/plugins/defaults/memory/prompt-override.ts +51 -13
- package/src/plugins/defaults/memory/tools.test.ts +34 -0
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
- package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
- package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
- package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
- package/src/plugins/defaults/memory/v2/migration.ts +46 -16
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
- package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
- package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
- package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
- package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
- package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
- package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
- package/src/plugins/defaults/memory/v3/prune.ts +17 -11
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
- package/src/plugins/external-plugin-loader.ts +15 -15
- package/src/plugins/mtime-cache.ts +47 -0
- package/src/plugins/pipeline.ts +9 -1
- package/src/plugins/plugin-execution-context.ts +44 -0
- package/src/plugins/plugin-tree-walk.ts +31 -24
- package/src/plugins/source-fingerprint.ts +3 -4
- package/src/prompts/normalize-onboarding.ts +12 -0
- package/src/prompts/persona-resolver.ts +8 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
- package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
- package/src/providers/__tests__/retry-callsite.test.ts +235 -215
- package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
- package/src/providers/atlascloud/client.ts +10 -49
- package/src/providers/baseten/client.ts +43 -0
- package/src/providers/call-site-routing.ts +26 -13
- package/src/providers/connection-resolution.ts +9 -11
- package/src/providers/fetch-provider-catalog.ts +4 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
- package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
- package/src/providers/inference/adapter-factory.ts +27 -6
- package/src/providers/inference/auth.ts +29 -1
- package/src/providers/inference/backfill.ts +61 -10
- package/src/providers/inference/connection-availability.ts +3 -2
- package/src/providers/inference/resolve-auth.ts +9 -1
- package/src/providers/model-catalog.ts +37 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
- package/src/providers/openai/api-key-validation.ts +70 -0
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +11 -4
- package/src/providers/vellum-model-routing.test.ts +26 -0
- package/src/providers/vellum-model-routing.ts +30 -0
- package/src/providers/voice-error-copy.ts +47 -0
- package/src/runtime/agent-wake.ts +40 -24
- package/src/runtime/for-chat-mint-registry.ts +118 -0
- package/src/runtime/reveal-nonce.ts +49 -0
- package/src/runtime/reveal-success-registry.ts +306 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
- package/src/runtime/routes/app-management-routes.ts +152 -45
- package/src/runtime/routes/app-routes.ts +16 -77
- package/src/runtime/routes/canned-message-complete.ts +16 -16
- package/src/runtime/routes/conversation-management-routes.ts +17 -14
- package/src/runtime/routes/conversation-query-routes.ts +54 -6
- package/src/runtime/routes/conversation-routes.ts +48 -2
- package/src/runtime/routes/credential-routes.ts +116 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
- package/src/runtime/routes/inference-send-routes.ts +1 -1
- package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
- package/src/runtime/routes/plugins-routes.ts +37 -6
- package/src/runtime/routes/publish-routes.ts +15 -18
- package/src/runtime/routes/schedule-worker-routes.ts +9 -0
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/telemetry-routes.ts +149 -45
- package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
- package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
- package/src/schedule/schedule-store.ts +12 -1
- package/src/schedule/schedule-timezone.ts +63 -0
- package/src/schedule/scheduler.ts +100 -6
- package/src/schedule/worker-control.ts +19 -0
- package/src/security/auth-fallback-events-store.ts +7 -6
- package/src/security/secret-scanner.ts +26 -1
- package/src/services/published-app-updater.ts +6 -11
- package/src/stt/stt-stream-session.ts +36 -16
- package/src/subagent/manager.ts +34 -8
- package/src/telemetry/AGENTS.md +30 -1
- package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
- package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
- package/src/telemetry/config-setting-snapshot.ts +44 -10
- package/src/telemetry/telemetry-event-sources.test.ts +124 -30
- package/src/telemetry/telemetry-event-sources.ts +137 -83
- package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
- package/src/telemetry/telemetry-events-outbox.ts +60 -9
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire-validation.ts +39 -2
- package/src/telemetry/telemetry-wire.generated.ts +8 -0
- package/src/telemetry/tool-audit.ts +15 -9
- package/src/telemetry/tool-executed-events-store.test.ts +1 -1
- package/src/telemetry/turn-events-store.ts +20 -0
- package/src/telemetry/types.ts +45 -12
- package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
- package/src/telemetry/usage-telemetry-reporter.ts +117 -18
- package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
- package/src/telemetry/watchdog-direct-emit.ts +11 -6
- package/src/tools/apps/executors.ts +11 -36
- package/src/tools/executor.ts +14 -1
- package/src/tools/host-terminal/host-shell.ts +6 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
- package/src/tools/network/web-fetch.ts +6 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/subagent/spawn.ts +7 -11
- package/src/tools/terminal/shell.ts +5 -2
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/tool-profiler.ts +37 -6
- package/src/tools/ui-surface/channel-variants.ts +101 -0
- package/src/tools/ui-surface/definitions.ts +23 -67
- package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
- package/src/tts/__tests__/provider-adapters.test.ts +18 -0
- package/src/tts/provider-catalog.ts +2 -4
- package/src/tts/providers/deepgram-provider.ts +10 -1
- package/src/types/onboarding-context.ts +8 -0
- package/src/usage/attribution.ts +18 -112
- package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
- package/src/util/provider-error-patterns.ts +7 -1
- package/src/util/worker-process.ts +105 -0
- package/src/watcher/__tests__/telemetry.test.ts +17 -4
- package/src/watcher/telemetry.ts +5 -5
- package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/provider-commit-message-generator.ts +6 -4
- package/src/__tests__/app-open-proxy.test.ts +0 -67
- package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
- package/src/onboarding/onboarding-research-events-store.ts +0 -104
- package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
- package/src/tools/apps/definitions.ts +0 -73
- package/src/tools/apps/open-proxy.ts +0 -43
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* for-chat-mint-registry — daemon-side record of `--for-chat` reveals that
|
|
3
|
+
* actually executed.
|
|
4
|
+
*
|
|
5
|
+
* The chat-credential-reveal persist guard re-mints sentinel-shaped spans
|
|
6
|
+
* whose `service:field` identity matches a `--for-chat` reveal from the
|
|
7
|
+
* current turn (see `guardForChatSentinels`). The authority for "a
|
|
8
|
+
* `--for-chat` reveal happened" must be the reveal ROUTE itself — the one
|
|
9
|
+
* place that enforces the feature flag and proves the credential exists —
|
|
10
|
+
* never a string parse of the requested shell command: a segment that
|
|
11
|
+
* merely quotes or comments out a reveal invocation (`echo 'assistant
|
|
12
|
+
* credentials reveal --for-chat …'`) would otherwise allowlist an identity
|
|
13
|
+
* for re-minting without the route ever running, letting forged sentinels
|
|
14
|
+
* survive persistence.
|
|
15
|
+
*
|
|
16
|
+
* So: `handleCredentialsReveal` records every successful `--for-chat` mint
|
|
17
|
+
* here (identity + the exact canonical sentinel it returned), and the
|
|
18
|
+
* conversation loop asks for mints recorded since its turn started. The
|
|
19
|
+
* stored sentinel doubles as the re-mint replacement — no vault re-read,
|
|
20
|
+
* and a tampered type label can't survive because the replacement is the
|
|
21
|
+
* daemon's own original mint.
|
|
22
|
+
*
|
|
23
|
+
* Scope: records are bound to the executing conversation by the reveal
|
|
24
|
+
* NONCE (see `reveal-nonce.ts`) — a daemon-held secret the shell tools
|
|
25
|
+
* export to the tool subprocess and the CLI forwards with the reveal. A
|
|
26
|
+
* predictable identifier (a conversation id in a body field or env var)
|
|
27
|
+
* would not do: the command under execution can override its subprocess
|
|
28
|
+
* env and redirect the mint to any conversation it can name, and staging
|
|
29
|
+
* alone cannot disambiguate CONCURRENT turns (staging is a parse — a turn
|
|
30
|
+
* that merely quotes a reveal command stages the same identity an
|
|
31
|
+
* overlapping turn legitimately executes). The consumer accepts a mint
|
|
32
|
+
* only when its nonce matches the consumer's own conversation AND its
|
|
33
|
+
* identity was staged by that run's own `tool_use` commands (see
|
|
34
|
+
* `turnForChatMints` in `conversation-agent-loop-handlers.ts`).
|
|
35
|
+
* `credentials reveal` is a high-risk, approval-gated command; earning a
|
|
36
|
+
* mint for a conversation requires an actual route call from that
|
|
37
|
+
* conversation's own tool shell.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** A successful `--for-chat` reveal: identity plus the minted sentinel. */
|
|
41
|
+
export interface ForChatMint {
|
|
42
|
+
service: string;
|
|
43
|
+
field: string;
|
|
44
|
+
/** The canonical sentinel the reveal route returned for this credential. */
|
|
45
|
+
sentinel: string;
|
|
46
|
+
/**
|
|
47
|
+
* The conversation-bound reveal nonce the CLI forwarded (see
|
|
48
|
+
* `reveal-nonce.ts`). Consumers match on it exactly.
|
|
49
|
+
*/
|
|
50
|
+
nonce: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface MintRecord extends ForChatMint {
|
|
54
|
+
seq: number;
|
|
55
|
+
atMs: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Retention guards. Records are tiny (one short sentinel string each), so
|
|
60
|
+
* the bounds exist only to keep a pathological reveal loop from growing the
|
|
61
|
+
* array unboundedly. The age bound comfortably outlives any single turn —
|
|
62
|
+
* a watermark from a turn older than this has long since been superseded.
|
|
63
|
+
*/
|
|
64
|
+
const MAX_RECORDS = 256;
|
|
65
|
+
const MAX_AGE_MS = 6 * 60 * 60 * 1000;
|
|
66
|
+
|
|
67
|
+
let counter = 0;
|
|
68
|
+
let records: MintRecord[] = [];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Watermark for "now". A turn captures this at start; mints recorded after
|
|
72
|
+
* the capture are the ones `forChatMintsSince` returns for that turn.
|
|
73
|
+
*/
|
|
74
|
+
export function currentForChatMintWatermark(): number {
|
|
75
|
+
return counter;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Record a successful `--for-chat` reveal. Called by the reveal route. */
|
|
79
|
+
export function recordForChatMint(mint: ForChatMint): void {
|
|
80
|
+
counter += 1;
|
|
81
|
+
records.push({ ...mint, seq: counter, atMs: Date.now() });
|
|
82
|
+
const cutoff = Date.now() - MAX_AGE_MS;
|
|
83
|
+
if (records.length > MAX_RECORDS || records[0]!.atMs < cutoff) {
|
|
84
|
+
records = records.filter((r) => r.atMs >= cutoff).slice(-MAX_RECORDS);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Mints recorded after `watermark`, deduplicated per nonce+identity (latest
|
|
90
|
+
* wins — one conversation re-revealing the same credential re-mints the
|
|
91
|
+
* same canonical sentinel). The nonce is part of the dedupe key on purpose:
|
|
92
|
+
* consumers filter the result by THEIR nonce afterwards, so an
|
|
93
|
+
* identity-only dedupe would let a concurrent conversation's later reveal
|
|
94
|
+
* of the same credential clobber this conversation's legitimate mint and
|
|
95
|
+
* neutralize its echoed sentinel. Callers MUST intersect the result with
|
|
96
|
+
* their own trusted staging context before treating a mint as re-mint
|
|
97
|
+
* authority — see the module doc.
|
|
98
|
+
*/
|
|
99
|
+
export function forChatMintsSince(watermark: number): ForChatMint[] {
|
|
100
|
+
const byNonceAndIdentity = new Map<string, ForChatMint>();
|
|
101
|
+
for (const r of records) {
|
|
102
|
+
if (r.seq > watermark) {
|
|
103
|
+
byNonceAndIdentity.set(`${r.nonce}\u0000${r.service}\u0000${r.field}`, {
|
|
104
|
+
service: r.service,
|
|
105
|
+
field: r.field,
|
|
106
|
+
sentinel: r.sentinel,
|
|
107
|
+
nonce: r.nonce,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...byNonceAndIdentity.values()];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Test-only: drop all records and reset the watermark counter. */
|
|
115
|
+
export function resetForChatMintRegistryForTest(): void {
|
|
116
|
+
counter = 0;
|
|
117
|
+
records = [];
|
|
118
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reveal-nonce — per-conversation secret binding reveal-derived authority
|
|
3
|
+
* to the conversation whose tool shell actually executed the reveal.
|
|
4
|
+
*
|
|
5
|
+
* The reveal route's records (plaintext proofs and `--for-chat` mints) are
|
|
6
|
+
* process-global, and the identities a run STAGES are parsed from command
|
|
7
|
+
* text — so with two turns running concurrently, conversation B could
|
|
8
|
+
* quote a reveal command (staging the identity) while conversation A
|
|
9
|
+
* executes a real reveal for the same identity, and B's guard would treat
|
|
10
|
+
* A's record as its own authority. Every caller-visible conversation
|
|
11
|
+
* identifier (`__CONVERSATION_ID`, a body field) is overridable by the
|
|
12
|
+
* command under execution, so the binding must be a value other
|
|
13
|
+
* conversations cannot know.
|
|
14
|
+
*
|
|
15
|
+
* This module holds one random nonce per conversation, created lazily and
|
|
16
|
+
* kept only in daemon memory. The shell tools export it to the tool
|
|
17
|
+
* subprocess as `__REVEAL_NONCE`; the CLI forwards it on reveal calls; the
|
|
18
|
+
* route stamps it onto every record; and the conversation loop accepts
|
|
19
|
+
* only records carrying its own nonce. A quoted/commented-out invocation
|
|
20
|
+
* never reaches the route, so it never produces a record with any nonce.
|
|
21
|
+
* A model can read its own conversation's nonce from the tool env, but
|
|
22
|
+
* spending it requires an actual route call from its own approval-gated
|
|
23
|
+
* tool shell — which is precisely the authority the binding encodes.
|
|
24
|
+
* Another conversation's nonce appears in no transcript, log, or URL;
|
|
25
|
+
* stealing one requires an exfiltration channel between two already-
|
|
26
|
+
* compromised conversations, which defeats any per-conversation binding.
|
|
27
|
+
*
|
|
28
|
+
* Nonces are not persisted: a daemon restart rotates them, which only
|
|
29
|
+
* invalidates records that are themselves turn-scoped and in-memory.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { randomBytes } from "node:crypto";
|
|
33
|
+
|
|
34
|
+
const nonces = new Map<string, string>();
|
|
35
|
+
|
|
36
|
+
/** The secret reveal nonce for a conversation (lazily created). */
|
|
37
|
+
export function conversationRevealNonce(conversationId: string): string {
|
|
38
|
+
let nonce = nonces.get(conversationId);
|
|
39
|
+
if (nonce === undefined) {
|
|
40
|
+
nonce = randomBytes(16).toString("hex");
|
|
41
|
+
nonces.set(conversationId, nonce);
|
|
42
|
+
}
|
|
43
|
+
return nonce;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Test-only: drop all nonces so each test starts unbound. */
|
|
47
|
+
export function _resetRevealNoncesForTest(): void {
|
|
48
|
+
nonces.clear();
|
|
49
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process registry of successful `credentials_reveal` route executions.
|
|
3
|
+
*
|
|
4
|
+
* The chat-credential-reveal persist seams need PROOF that a reveal
|
|
5
|
+
* actually ran before candidate resolution may read a plaintext from the
|
|
6
|
+
* store: a shell tool's overall success is not that proof (`assistant
|
|
7
|
+
* credentials reveal … || true` succeeds when the route failed, and an
|
|
8
|
+
* `echo` containing the command text never calls the route at all). The
|
|
9
|
+
* reveal route handler is the single place plaintext legitimately leaves
|
|
10
|
+
* the store for a reveal, so it records each success here and the agent
|
|
11
|
+
* loop promotes staged candidate refs only when a matching identity was
|
|
12
|
+
* recorded between the tool's proposal and its result.
|
|
13
|
+
*
|
|
14
|
+
* Watermarks are a monotonic sequence counter: callers capture the
|
|
15
|
+
* watermark when a tool_use stages refs and query "did identity X succeed
|
|
16
|
+
* since?" at result time — records from earlier turns or long-dead tools
|
|
17
|
+
* never match. The registry is process-local (route handlers and the
|
|
18
|
+
* conversation loop share the daemon process) and bounded by count and
|
|
19
|
+
* age so it cannot grow without limit (recent records get a short grace
|
|
20
|
+
* from the count cap so an active tool window cannot lose its proof).
|
|
21
|
+
*
|
|
22
|
+
* Only DIRECT local-principal reveals record here (the unix-socket IPC
|
|
23
|
+
* identity a tool shell's CLI invocation carries — see the route handler's
|
|
24
|
+
* gate): a web or gateway-proxied reveal (Settings row, chat chips) is not
|
|
25
|
+
* evidence any tool ran a reveal and must never become proof — including
|
|
26
|
+
* local-mode web calls, where the gateway derives the `local` principal
|
|
27
|
+
* from the JWT but always stamps `x-vellum-proxy-server: ipc`.
|
|
28
|
+
*
|
|
29
|
+
* Records are conversation-bound via the reveal NONCE (see
|
|
30
|
+
* `reveal-nonce.ts`): the shell tools export each conversation's secret
|
|
31
|
+
* nonce to the tool subprocess, the CLI forwards it, the route stamps it
|
|
32
|
+
* onto the record, and readers require an exact match. A concurrent
|
|
33
|
+
* conversation's reveal of the SAME identity therefore cannot satisfy
|
|
34
|
+
* another conversation's proof check — its records carry a different
|
|
35
|
+
* nonce — and a reveal with no nonce (outside any conversation's tool
|
|
36
|
+
* shell) records nothing.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
interface RevealSuccessRecord {
|
|
40
|
+
readonly seq: number;
|
|
41
|
+
readonly service: string;
|
|
42
|
+
readonly field: string;
|
|
43
|
+
/**
|
|
44
|
+
* The plaintext the route actually served for this success. Retained so the
|
|
45
|
+
* persist seams redact the EXACT bytes the tool printed, rather than a later
|
|
46
|
+
* vault read that a rotation/deletion between reveal and persist could make
|
|
47
|
+
* diverge (or empty). This is the same secret the model already received in
|
|
48
|
+
* the tool result — holding it in the process-local, count-and-age-bounded
|
|
49
|
+
* registry for the length of one turn adds no new exposure surface.
|
|
50
|
+
*/
|
|
51
|
+
readonly value: string;
|
|
52
|
+
/**
|
|
53
|
+
* The conversation-bound reveal nonce the CLI forwarded (see
|
|
54
|
+
* `reveal-nonce.ts`). Readers match on it exactly, so a record can only
|
|
55
|
+
* ever prove refs for the conversation whose tool shell executed the
|
|
56
|
+
* reveal.
|
|
57
|
+
*/
|
|
58
|
+
readonly nonce: string;
|
|
59
|
+
readonly recordedAtMs: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const MAX_RECORDS = 256;
|
|
63
|
+
const MAX_AGE_MS = 6 * 60 * 60 * 1000;
|
|
64
|
+
/**
|
|
65
|
+
* Records younger than this are exempt from the count cap: a success may
|
|
66
|
+
* still be inside an active propose→result window (a single command with
|
|
67
|
+
* hundreds of reveal invocations, or concurrent turns), and count-evicting
|
|
68
|
+
* it would silently drop the proof — the tool's stdout then persists raw.
|
|
69
|
+
* Memory stays bounded: the age prune always applies, and the count cap
|
|
70
|
+
* resumes for anything older than the grace period.
|
|
71
|
+
*/
|
|
72
|
+
const COUNT_PRUNE_GRACE_MS = 15 * 60 * 1000;
|
|
73
|
+
|
|
74
|
+
let seqCounter = 0;
|
|
75
|
+
let records: RevealSuccessRecord[] = [];
|
|
76
|
+
/**
|
|
77
|
+
* Evicts expired records even when the registry goes idle. Records hold
|
|
78
|
+
* credential plaintext, and pruning only on the next write would retain a
|
|
79
|
+
* lone reveal's secret in memory indefinitely — there may never be another
|
|
80
|
+
* reveal. Armed on every write for the earliest record's expiry,
|
|
81
|
+
* self-reschedules while records remain, and unref'd so it never holds the
|
|
82
|
+
* process open. Reads prune too, so an active conversation drops expired
|
|
83
|
+
* records without waiting for the timer.
|
|
84
|
+
*/
|
|
85
|
+
let pruneTimer: ReturnType<typeof setTimeout> | undefined;
|
|
86
|
+
|
|
87
|
+
function prune(nowMs: number): void {
|
|
88
|
+
records = records.filter((r) => nowMs - r.recordedAtMs <= MAX_AGE_MS);
|
|
89
|
+
let excess = records.length - MAX_RECORDS;
|
|
90
|
+
if (excess > 0) {
|
|
91
|
+
// Oldest-first eviction (records are in seq order), skipping anything
|
|
92
|
+
// still inside the grace window.
|
|
93
|
+
records = records.filter((r) => {
|
|
94
|
+
if (excess > 0 && nowMs - r.recordedAtMs > COUNT_PRUNE_GRACE_MS) {
|
|
95
|
+
excess -= 1;
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function schedulePrune(nowMs: number): void {
|
|
104
|
+
if (pruneTimer !== undefined || records.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const earliestExpiryMs = records[0]!.recordedAtMs + MAX_AGE_MS;
|
|
108
|
+
const timer = setTimeout(
|
|
109
|
+
() => {
|
|
110
|
+
pruneTimer = undefined;
|
|
111
|
+
const t = Date.now();
|
|
112
|
+
prune(t);
|
|
113
|
+
schedulePrune(t);
|
|
114
|
+
},
|
|
115
|
+
Math.max(earliestExpiryMs - nowMs, 1000),
|
|
116
|
+
);
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
pruneTimer = timer;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Current watermark. Capture BEFORE the reveal tool executes; only
|
|
123
|
+
* successes recorded after this point satisfy {@link hasRevealSuccessSince}.
|
|
124
|
+
*/
|
|
125
|
+
export function currentRevealSuccessWatermark(): number {
|
|
126
|
+
return seqCounter;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Open proof windows — one per tool_use that STAGED reveal refs, closed
|
|
131
|
+
* when its tool_result consumes the proof. Plaintext is only ever recorded
|
|
132
|
+
* while at least one window is open: a user's own local CLI reveal outside
|
|
133
|
+
* any assistant tool turn has no pending proof to satisfy, and retaining
|
|
134
|
+
* its value for the age bound would expand every CLI reveal's exposure for
|
|
135
|
+
* nothing. Windows auto-expire at the age bound so a leaked window (a tool
|
|
136
|
+
* whose result never arrives) cannot arm recording forever.
|
|
137
|
+
*/
|
|
138
|
+
interface RevealProofWindow {
|
|
139
|
+
readonly token: number;
|
|
140
|
+
readonly openedAtMs: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let windowCounter = 0;
|
|
144
|
+
let openWindows: RevealProofWindow[] = [];
|
|
145
|
+
|
|
146
|
+
function expireWindows(nowMs: number): void {
|
|
147
|
+
openWindows = openWindows.filter((w) => nowMs - w.openedAtMs <= MAX_AGE_MS);
|
|
148
|
+
clearRecordsIfNoWindows();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Drop every retained record the moment no proof window remains open.
|
|
153
|
+
* Proof consumption is the records' only purpose, and once the last
|
|
154
|
+
* window closes nothing can ever read them again: the consuming
|
|
155
|
+
* `filterRefsByRevealProof` runs BEFORE its window is closed, promoted
|
|
156
|
+
* refs carry the plaintext themselves, and any future staging captures a
|
|
157
|
+
* watermark at the current sequence counter — above every existing
|
|
158
|
+
* record's seq. Waiting for the age bound would keep credential plaintext
|
|
159
|
+
* in memory long after the proof was needed.
|
|
160
|
+
*/
|
|
161
|
+
function clearRecordsIfNoWindows(): void {
|
|
162
|
+
if (openWindows.length > 0 || records.length === 0) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
records = [];
|
|
166
|
+
if (pruneTimer !== undefined) {
|
|
167
|
+
clearTimeout(pruneTimer);
|
|
168
|
+
pruneTimer = undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Open a proof window. Call when a tool_use stages reveal refs — BEFORE
|
|
174
|
+
* the tool executes, so the route's success during the run is recordable.
|
|
175
|
+
* Returns a token the staging seam must pass to
|
|
176
|
+
* {@link closeRevealProofWindow} when the tool's result consumes the proof.
|
|
177
|
+
*/
|
|
178
|
+
export function openRevealProofWindow(): number {
|
|
179
|
+
expireWindows(Date.now());
|
|
180
|
+
windowCounter += 1;
|
|
181
|
+
openWindows.push({ token: windowCounter, openedAtMs: Date.now() });
|
|
182
|
+
return windowCounter;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Close a proof window once its staged refs have been consumed. */
|
|
186
|
+
export function closeRevealProofWindow(token: number): void {
|
|
187
|
+
openWindows = openWindows.filter((w) => w.token !== token);
|
|
188
|
+
clearRecordsIfNoWindows();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Record a successful reveal. Call ONLY from the `credentials_reveal`
|
|
193
|
+
* route handler, after the plaintext has been located AND the caller was
|
|
194
|
+
* verified as the `local` principal (a tool shell's direct-IPC CLI call) —
|
|
195
|
+
* this is the ground truth the promotion check trusts. `value` is the
|
|
196
|
+
* plaintext the route served, retained so persist redacts the exact
|
|
197
|
+
* printed bytes. A no-op while no proof window is open — with no staged
|
|
198
|
+
* tool reveal anywhere, there is nothing the plaintext could prove.
|
|
199
|
+
*/
|
|
200
|
+
export function recordRevealSuccess(
|
|
201
|
+
service: string,
|
|
202
|
+
field: string,
|
|
203
|
+
value: string,
|
|
204
|
+
nonce: string,
|
|
205
|
+
): void {
|
|
206
|
+
const nowMs = Date.now();
|
|
207
|
+
expireWindows(nowMs);
|
|
208
|
+
if (openWindows.length === 0) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
seqCounter += 1;
|
|
212
|
+
records.push({
|
|
213
|
+
seq: seqCounter,
|
|
214
|
+
service,
|
|
215
|
+
field,
|
|
216
|
+
value,
|
|
217
|
+
nonce,
|
|
218
|
+
recordedAtMs: nowMs,
|
|
219
|
+
});
|
|
220
|
+
prune(nowMs);
|
|
221
|
+
schedulePrune(nowMs);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Whether the reveal route succeeded for `service`/`field` after
|
|
226
|
+
* `watermark` was captured.
|
|
227
|
+
*/
|
|
228
|
+
export function hasRevealSuccessSince(
|
|
229
|
+
watermark: number,
|
|
230
|
+
service: string,
|
|
231
|
+
field: string,
|
|
232
|
+
nonce: string,
|
|
233
|
+
): boolean {
|
|
234
|
+
return revealedValueSince(watermark, service, field, nonce) !== undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The most recent plaintext the reveal route served for `service`/`field`
|
|
239
|
+
* after `watermark`, or `undefined` if no matching success was recorded.
|
|
240
|
+
*/
|
|
241
|
+
export function revealedValueSince(
|
|
242
|
+
watermark: number,
|
|
243
|
+
service: string,
|
|
244
|
+
field: string,
|
|
245
|
+
nonce: string,
|
|
246
|
+
): string | undefined {
|
|
247
|
+
return revealedValuesSince(watermark, service, field, nonce)[0];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* ALL distinct plaintexts the reveal route served for `service`/`field`
|
|
252
|
+
* after `watermark`, most recent first. Usually a single value, but a
|
|
253
|
+
* rotate-and-re-reveal inside one window (reveal v1 → `credentials set` →
|
|
254
|
+
* reveal v2) legitimately serves two different plaintexts — both were
|
|
255
|
+
* printed to a tool's stdout, so persist redaction must treat each as a
|
|
256
|
+
* candidate; surfacing only the latest would leave the earlier bytes
|
|
257
|
+
* unprotected. Callers use these to redact the exact served bytes instead
|
|
258
|
+
* of re-reading the vault, which a rotation/deletion between reveal and
|
|
259
|
+
* persist could make diverge.
|
|
260
|
+
*/
|
|
261
|
+
export function revealedValuesSince(
|
|
262
|
+
watermark: number,
|
|
263
|
+
service: string,
|
|
264
|
+
field: string,
|
|
265
|
+
nonce: string,
|
|
266
|
+
): string[] {
|
|
267
|
+
const nowMs = Date.now();
|
|
268
|
+
// Evict, don't just filter: expired records hold plaintext and must
|
|
269
|
+
// leave memory as soon as any registry activity notices them.
|
|
270
|
+
prune(nowMs);
|
|
271
|
+
const matches = records
|
|
272
|
+
.filter(
|
|
273
|
+
(r) =>
|
|
274
|
+
r.seq > watermark &&
|
|
275
|
+
r.service === service &&
|
|
276
|
+
r.field === field &&
|
|
277
|
+
r.nonce === nonce,
|
|
278
|
+
)
|
|
279
|
+
.sort((a, b) => b.seq - a.seq);
|
|
280
|
+
const seen = new Set<string>();
|
|
281
|
+
const values: string[] = [];
|
|
282
|
+
for (const r of matches) {
|
|
283
|
+
if (!seen.has(r.value)) {
|
|
284
|
+
seen.add(r.value);
|
|
285
|
+
values.push(r.value);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return values;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Test-only: number of retained records, for retention assertions. */
|
|
292
|
+
export function _recordCountForTest(): number {
|
|
293
|
+
return records.length;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Test-only: clear records, windows, the prune timer, and the watermark. */
|
|
297
|
+
export function _resetRevealSuccessRegistryForTest(): void {
|
|
298
|
+
seqCounter = 0;
|
|
299
|
+
records = [];
|
|
300
|
+
windowCounter = 0;
|
|
301
|
+
openWindows = [];
|
|
302
|
+
if (pruneTimer !== undefined) {
|
|
303
|
+
clearTimeout(pruneTimer);
|
|
304
|
+
pruneTimer = undefined;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -53,13 +53,16 @@ mock.module("../../../persistence/embeddings/embedding-backend.js", () => ({
|
|
|
53
53
|
import { loadRawConfig } from "../../../config/loader.js";
|
|
54
54
|
import { LLMConfigBase } from "../../../config/schemas/llm.js";
|
|
55
55
|
import type { ConversationCreateType } from "../../../persistence/conversation-types.js";
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
getDb,
|
|
58
|
+
getLogsDb,
|
|
59
|
+
getMemorySqlite,
|
|
60
|
+
} from "../../../persistence/db-connection.js";
|
|
57
61
|
import { initializeDb } from "../../../persistence/db-init.js";
|
|
58
62
|
import {
|
|
59
63
|
conversationKeys,
|
|
60
64
|
conversations,
|
|
61
65
|
llmRequestLogs,
|
|
62
|
-
memoryV2ActivationLogs,
|
|
63
66
|
messages,
|
|
64
67
|
providerConnections,
|
|
65
68
|
} from "../../../persistence/schema/index.js";
|
|
@@ -106,7 +109,7 @@ function dispatchConversationLlmContext(queryParams: Record<string, string>) {
|
|
|
106
109
|
function clearTables(): void {
|
|
107
110
|
const db = getDb();
|
|
108
111
|
getLogsDb()!.delete(llmRequestLogs).run();
|
|
109
|
-
|
|
112
|
+
getMemorySqlite()!.exec(`DELETE FROM memory_v2_activation_logs`);
|
|
110
113
|
db.delete(messages).run();
|
|
111
114
|
db.delete(conversationKeys).run();
|
|
112
115
|
db.delete(conversations).run();
|
|
@@ -1396,6 +1399,88 @@ describe("custom profile write normalization (complete overrides)", () => {
|
|
|
1396
1399
|
});
|
|
1397
1400
|
});
|
|
1398
1401
|
|
|
1402
|
+
describe("call-site override tuning backfill", () => {
|
|
1403
|
+
const configPatchRoute = ROUTES.find(
|
|
1404
|
+
(r) => r.operationId === "config_patch",
|
|
1405
|
+
)!;
|
|
1406
|
+
|
|
1407
|
+
const savedCallSites = () =>
|
|
1408
|
+
(
|
|
1409
|
+
loadRawConfig().llm as {
|
|
1410
|
+
callSites?: Record<string, Record<string, unknown>>;
|
|
1411
|
+
}
|
|
1412
|
+
).callSites ?? {};
|
|
1413
|
+
|
|
1414
|
+
beforeEach(() => {
|
|
1415
|
+
rawConfigFixture = { llm: {} };
|
|
1416
|
+
seedRawConfig();
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
test("PATCH creating a bare { profile } entry backfills shipped tuning", async () => {
|
|
1420
|
+
await configPatchRoute.handler({
|
|
1421
|
+
body: {
|
|
1422
|
+
llm: {
|
|
1423
|
+
callSites: {
|
|
1424
|
+
memoryRouter: { profile: "mine", provider: null, model: null },
|
|
1425
|
+
commitMessage: { profile: "mine", provider: null, model: null },
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
},
|
|
1429
|
+
});
|
|
1430
|
+
const memoryRouter = savedCallSites().memoryRouter!;
|
|
1431
|
+
expect(memoryRouter.profile).toBe("mine");
|
|
1432
|
+
expect(memoryRouter.contextWindow).toEqual({ maxInputTokens: 1_000_000 });
|
|
1433
|
+
const commitMessage = savedCallSites().commitMessage!;
|
|
1434
|
+
expect(commitMessage.profile).toBe("mine");
|
|
1435
|
+
expect(commitMessage.maxTokens).toBe(120);
|
|
1436
|
+
expect(commitMessage.effort).toBe("low");
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
test("explicit patch values win over shipped tuning on a new entry", async () => {
|
|
1440
|
+
await configPatchRoute.handler({
|
|
1441
|
+
body: {
|
|
1442
|
+
llm: {
|
|
1443
|
+
callSites: { commitMessage: { profile: "mine", maxTokens: 500 } },
|
|
1444
|
+
},
|
|
1445
|
+
},
|
|
1446
|
+
});
|
|
1447
|
+
const saved = savedCallSites().commitMessage!;
|
|
1448
|
+
expect(saved.maxTokens).toBe(500);
|
|
1449
|
+
expect(saved.temperature).toBe(0.2);
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
test("existing entries are never backfilled — customization is preserved", async () => {
|
|
1453
|
+
rawConfigFixture = {
|
|
1454
|
+
llm: { callSites: { recall: { profile: "old", maxTokens: 200 } } },
|
|
1455
|
+
};
|
|
1456
|
+
seedRawConfig();
|
|
1457
|
+
await configPatchRoute.handler({
|
|
1458
|
+
body: {
|
|
1459
|
+
llm: {
|
|
1460
|
+
callSites: {
|
|
1461
|
+
recall: { profile: "mine", provider: null, model: null },
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1464
|
+
},
|
|
1465
|
+
});
|
|
1466
|
+
const saved = savedCallSites().recall!;
|
|
1467
|
+
expect(saved.profile).toBe("mine");
|
|
1468
|
+
expect(saved.maxTokens).toBe(200);
|
|
1469
|
+
expect(saved.disableCache).toBeUndefined();
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
test("deleting an entry (null) is untouched by the backfill", async () => {
|
|
1473
|
+
rawConfigFixture = {
|
|
1474
|
+
llm: { callSites: { recall: { profile: "old" } } },
|
|
1475
|
+
};
|
|
1476
|
+
seedRawConfig();
|
|
1477
|
+
await configPatchRoute.handler({
|
|
1478
|
+
body: { llm: { callSites: { recall: null } } },
|
|
1479
|
+
});
|
|
1480
|
+
expect(savedCallSites().recall).toBeUndefined();
|
|
1481
|
+
});
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1399
1484
|
describe("config invariant flag enrichment", () => {
|
|
1400
1485
|
const configGetRoute = ROUTES.find((r) => r.operationId === "config_get")!;
|
|
1401
1486
|
const configPatchRoute = ROUTES.find(
|