@vellumai/assistant 0.7.2 → 0.8.0
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 +45 -29
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/docs/architecture/memory.md +5 -2
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +470 -25
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +6 -33
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +12 -7
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +457 -8
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +38 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +2 -20
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +36 -16
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +60 -5
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-request-resolvers.ts +3 -32
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +11 -10
- package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
- package/src/cli/commands/oauth/connect.ts +124 -40
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +199 -27
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +76 -12
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
- package/src/daemon/conversation-agent-loop.ts +183 -43
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +22 -10
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +211 -29
- package/src/daemon/conversation-tool-setup.ts +66 -19
- package/src/daemon/conversation.ts +18 -23
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +26 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +47 -22
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +50 -4
- package/src/daemon/host-file-proxy.ts +44 -8
- package/src/daemon/host-transfer-proxy.ts +97 -6
- package/src/daemon/lifecycle.ts +167 -101
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +66 -15
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +22 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +119 -0
- package/src/filing/filing-service.ts +29 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +149 -38
- package/src/ipc/gateway-client.ts +37 -3
- package/src/ipc/skill-server.ts +99 -42
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +1 -16
- package/src/memory/context-search/sources/memory.ts +2 -3
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +72 -61
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +11 -67
- package/src/memory/graph/graph-search.ts +4 -24
- package/src/memory/graph/retriever.test.ts +12 -1
- package/src/memory/graph/retriever.ts +10 -15
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +53 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +85 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +7 -0
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +10 -72
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +346 -255
- package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
- package/src/memory/v2/__tests__/injection.test.ts +297 -190
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +113 -196
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +77 -14
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +69 -20
- package/src/memory/v2/injection.ts +75 -68
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +41 -1
- package/src/memory/v2/qdrant.ts +306 -46
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +77 -110
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +26 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +17 -10
- package/src/notifications/copy-composer.ts +47 -0
- package/src/notifications/decision-engine.ts +46 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +36 -4
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +914 -0
- package/src/proactive-artifact/job.ts +366 -0
- package/src/proactive-artifact/message-copy.ts +58 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +27 -15
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +165 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +37 -17
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +136 -17
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/include-graph.ts +35 -13
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +14 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +43 -3
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- package/src/workspace/migrations/registry.ts +14 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -149,6 +149,19 @@ export const MemoryCleanupConfigSchema = z
|
|
|
149
149
|
.describe(
|
|
150
150
|
"Retention period for LLM request/response logs in milliseconds (null keeps forever, 0 prunes immediately)",
|
|
151
151
|
),
|
|
152
|
+
traceEventRetentionDays: z
|
|
153
|
+
.number({
|
|
154
|
+
error: "memory.cleanup.traceEventRetentionDays must be a number",
|
|
155
|
+
})
|
|
156
|
+
.int("memory.cleanup.traceEventRetentionDays must be an integer")
|
|
157
|
+
.nonnegative(
|
|
158
|
+
"memory.cleanup.traceEventRetentionDays must be non-negative",
|
|
159
|
+
)
|
|
160
|
+
.max(365, "memory.cleanup.traceEventRetentionDays must be <= 365 days")
|
|
161
|
+
.default(3)
|
|
162
|
+
.describe(
|
|
163
|
+
"Number of days to retain trace events before cleanup (0 disables pruning)",
|
|
164
|
+
),
|
|
152
165
|
})
|
|
153
166
|
.describe("Automatic memory cleanup and garbage collection settings");
|
|
154
167
|
|
|
@@ -7,6 +7,33 @@ import { z } from "zod";
|
|
|
7
7
|
*/
|
|
8
8
|
const WEIGHT_SUM_TOLERANCE = 0.001;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Default cross-encoder model for memory v2 reranking.
|
|
12
|
+
* `Alibaba-NLP/gte-reranker-modernbert-base` (149M, Apache-2.0) — 2025
|
|
13
|
+
* ModernBERT-backbone reranker; smaller, newer, and cleaner-licensed than
|
|
14
|
+
* the bge family while matching or beating their retrieval-benchmark scores.
|
|
15
|
+
* Has ONNX exports at the standard `onnx/model.onnx` path.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_RERANK_MODEL = "Alibaba-NLP/gte-reranker-modernbert-base";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ONNX weight precision passed to `@huggingface/transformers`. Sourced from
|
|
21
|
+
* transformers.js's supported `dtype` values; `q8` (int8) is ~3× faster than
|
|
22
|
+
* `fp32` on CPU with negligible reranker accuracy loss. Single source of
|
|
23
|
+
* truth for both the schema enum and the `LocalRerankBackend` type.
|
|
24
|
+
*/
|
|
25
|
+
export const RerankDtypeEnum = z.enum([
|
|
26
|
+
"fp32",
|
|
27
|
+
"fp16",
|
|
28
|
+
"q8",
|
|
29
|
+
"int8",
|
|
30
|
+
"uint8",
|
|
31
|
+
"q4",
|
|
32
|
+
"bnb4",
|
|
33
|
+
"q4f16",
|
|
34
|
+
]);
|
|
35
|
+
export type RerankDtype = z.infer<typeof RerankDtypeEnum>;
|
|
36
|
+
|
|
10
37
|
/**
|
|
11
38
|
* Memory v2 (concept-page activation model) configuration.
|
|
12
39
|
*
|
|
@@ -21,9 +48,9 @@ export const MemoryV2ConfigSchema = z
|
|
|
21
48
|
.object({
|
|
22
49
|
enabled: z
|
|
23
50
|
.boolean({ error: "memory.v2.enabled must be a boolean" })
|
|
24
|
-
.default(
|
|
51
|
+
.default(true)
|
|
25
52
|
.describe(
|
|
26
|
-
"Whether the v2 memory subsystem (concept-page activation model) is enabled.
|
|
53
|
+
"Whether the v2 memory subsystem (concept-page activation model) is enabled.",
|
|
27
54
|
),
|
|
28
55
|
sweep_enabled: z
|
|
29
56
|
.boolean({ error: "memory.v2.sweep_enabled must be a boolean" })
|
|
@@ -83,9 +110,9 @@ export const MemoryV2ConfigSchema = z
|
|
|
83
110
|
.number({ error: "memory.v2.top_k must be a number" })
|
|
84
111
|
.int("memory.v2.top_k must be an integer")
|
|
85
112
|
.positive("memory.v2.top_k must be a positive integer")
|
|
86
|
-
.default(
|
|
113
|
+
.default(25)
|
|
87
114
|
.describe(
|
|
88
|
-
"Number of top-activation concept pages considered for injection per turn",
|
|
115
|
+
"Number of top-activation entries (concept pages and skills combined) considered for injection per turn. Skills are scored alongside concepts in the same pool; this cap covers both.",
|
|
89
116
|
),
|
|
90
117
|
ann_candidate_limit: z
|
|
91
118
|
.number({ error: "memory.v2.ann_candidate_limit must be a number" })
|
|
@@ -96,14 +123,6 @@ export const MemoryV2ConfigSchema = z
|
|
|
96
123
|
.describe(
|
|
97
124
|
"Per-channel cap on the unrestricted ANN candidate query (dense and sparse each return up to this many hits before they are unioned and fed into the activation pipeline). `null` = unlimited (every page in the v2 collection is eligible). Increase or null this out to surface more candidates at the cost of higher per-turn embedding/scoring work.",
|
|
98
125
|
),
|
|
99
|
-
top_k_skills: z
|
|
100
|
-
.number({ error: "memory.v2.top_k_skills must be a number" })
|
|
101
|
-
.int()
|
|
102
|
-
.nonnegative()
|
|
103
|
-
.default(5)
|
|
104
|
-
.describe(
|
|
105
|
-
"Cap on the per-turn skill-autoinjection slate rendered in `### Skills You Can Use`. 0 disables skill autoinjection without code changes.",
|
|
106
|
-
),
|
|
107
126
|
epsilon: z
|
|
108
127
|
.number({ error: "memory.v2.epsilon must be a number" })
|
|
109
128
|
.min(0, "memory.v2.epsilon must be >= 0")
|
|
@@ -192,6 +211,51 @@ export const MemoryV2ConfigSchema = z
|
|
|
192
211
|
.describe(
|
|
193
212
|
"Optional path to a file whose contents replace the bundled consolidation prompt. Absolute paths are used as-is, a leading `~/` is expanded to the home directory, otherwise the path is resolved under the workspace root. The loaded contents may include `{{CUTOFF}}`, which is substituted with the run's ISO-8601 cutoff timestamp. If the file is missing, unreadable, or empty, the bundled prompt is used and a warning is logged.",
|
|
194
213
|
),
|
|
214
|
+
rerank: z
|
|
215
|
+
.object({
|
|
216
|
+
enabled: z
|
|
217
|
+
.boolean()
|
|
218
|
+
.default(false)
|
|
219
|
+
.describe(
|
|
220
|
+
"Whether to apply cross-encoder reranking as an additive A_o boost on the user + assistant channels. Disabled by default — opt in once measured.",
|
|
221
|
+
),
|
|
222
|
+
top_k: z
|
|
223
|
+
.number()
|
|
224
|
+
.int()
|
|
225
|
+
.positive()
|
|
226
|
+
.max(200)
|
|
227
|
+
.default(50)
|
|
228
|
+
.describe(
|
|
229
|
+
"Number of candidates from the top of the pre-rerank-A_o pool to send through the reranker. Tail candidates contribute zero rerank boost and keep their pure fused activation.",
|
|
230
|
+
),
|
|
231
|
+
alpha: z
|
|
232
|
+
.number()
|
|
233
|
+
.min(0)
|
|
234
|
+
.max(1)
|
|
235
|
+
.default(0.3)
|
|
236
|
+
.describe(
|
|
237
|
+
"Per-channel rerank weight: each top-K slug gets `alpha · normalized_rerank` added to A_o weighted by `c_user` (user channel) or `c_assistant` (assistant channel). Top reranker hit can lift A_o by up to `(c_user + c_assistant) · alpha`; bottom of top_k stays roughly unchanged.",
|
|
238
|
+
),
|
|
239
|
+
model: z
|
|
240
|
+
.string()
|
|
241
|
+
.default(DEFAULT_RERANK_MODEL)
|
|
242
|
+
.describe(
|
|
243
|
+
"HuggingFace model id for the cross-encoder. Must have an ONNX export reachable from huggingface.co/<model>/resolve/main/onnx/model.onnx.",
|
|
244
|
+
),
|
|
245
|
+
dtype: RerankDtypeEnum.default("q8").describe(
|
|
246
|
+
"ONNX weight precision passed to `@huggingface/transformers`. `q8` (int8) is ~3× faster than `fp32` on CPU with negligible reranker accuracy loss. The worker fails to spawn if the configured model has no matching quantized export — `reranker.ts` then falls back to pure fused scores for the turn.",
|
|
247
|
+
),
|
|
248
|
+
})
|
|
249
|
+
.default({
|
|
250
|
+
enabled: false,
|
|
251
|
+
top_k: 50,
|
|
252
|
+
alpha: 0.3,
|
|
253
|
+
model: DEFAULT_RERANK_MODEL,
|
|
254
|
+
dtype: "q8",
|
|
255
|
+
})
|
|
256
|
+
.describe(
|
|
257
|
+
"Cross-encoder rerank configuration. When enabled, picks the top-K candidates by pre-rerank A_o, runs the cross-encoder once per channel (user, assistant) on that unified set, and adds an alpha-weighted normalized boost to A_o for each scored slug.",
|
|
258
|
+
),
|
|
195
259
|
})
|
|
196
260
|
.describe(
|
|
197
261
|
"Memory v2 — concept-page activation model with hourly LLM-driven consolidation",
|
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
const IANA_TIMEZONE_IDENTIFIER_RE =
|
|
4
|
+
/^(?:UTC|[A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+)+)$/;
|
|
5
|
+
|
|
6
|
+
function canonicalizeIanaTimezone(timezone: string): string | null {
|
|
7
|
+
const trimmed = timezone.trim();
|
|
8
|
+
if (trimmed.length === 0) {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
if (!IANA_TIMEZONE_IDENTIFIER_RE.test(trimmed)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
16
|
+
timeZone: trimmed,
|
|
17
|
+
}).resolvedOptions().timeZone;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function timezoneConfigField(path: string) {
|
|
24
|
+
return z
|
|
25
|
+
.string({ error: `${path} must be a string` })
|
|
26
|
+
.transform((value, ctx) => {
|
|
27
|
+
const canonical = canonicalizeIanaTimezone(value);
|
|
28
|
+
if (canonical === null) {
|
|
29
|
+
ctx.addIssue({
|
|
30
|
+
code: "custom",
|
|
31
|
+
message: `${path} must be a valid IANA timezone identifier or an empty string`,
|
|
32
|
+
});
|
|
33
|
+
return z.NEVER;
|
|
34
|
+
}
|
|
35
|
+
return canonical;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
3
39
|
export const PlatformConfigSchema = z
|
|
4
40
|
.object({
|
|
5
41
|
baseUrl: z
|
|
@@ -56,11 +92,15 @@ export const DaemonConfigSchema = z
|
|
|
56
92
|
|
|
57
93
|
export const UiConfigSchema = z
|
|
58
94
|
.object({
|
|
59
|
-
userTimezone:
|
|
60
|
-
.
|
|
95
|
+
userTimezone: timezoneConfigField("ui.userTimezone")
|
|
96
|
+
.optional()
|
|
97
|
+
.describe(
|
|
98
|
+
"Manual IANA timezone override used for assistant temporal grounding and date/time display (e.g. 'America/New_York'). Use an empty string to clear the setting.",
|
|
99
|
+
),
|
|
100
|
+
detectedTimezone: timezoneConfigField("ui.detectedTimezone")
|
|
61
101
|
.optional()
|
|
62
102
|
.describe(
|
|
63
|
-
"IANA timezone identifier for
|
|
103
|
+
"IANA timezone identifier detected from the client environment for assistant temporal grounding when no manual override is configured (e.g. 'America/New_York'). Use an empty string to clear the setting.",
|
|
64
104
|
),
|
|
65
105
|
})
|
|
66
106
|
.describe(
|
|
@@ -72,6 +72,22 @@ const TwitterOAuthServiceSchema = BaseServiceSchema.extend({
|
|
|
72
72
|
mode: ServiceModeSchema.default("your-own"),
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
const AsanaOAuthServiceSchema = BaseServiceSchema.extend({
|
|
76
|
+
mode: ServiceModeSchema.default("your-own"),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const TodoistOAuthServiceSchema = BaseServiceSchema.extend({
|
|
80
|
+
mode: ServiceModeSchema.default("your-own"),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const DiscordOAuthServiceSchema = BaseServiceSchema.extend({
|
|
84
|
+
mode: ServiceModeSchema.default("your-own"),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const HubspotOAuthServiceSchema = BaseServiceSchema.extend({
|
|
88
|
+
mode: ServiceModeSchema.default("your-own"),
|
|
89
|
+
});
|
|
90
|
+
|
|
75
91
|
/**
|
|
76
92
|
* `services.meet.host.*` — daemon-side knobs for the externalized meet-join
|
|
77
93
|
* skill process. Kept narrow: only the values the daemon reads before the
|
|
@@ -140,6 +156,18 @@ export const ServicesSchema = z.object({
|
|
|
140
156
|
"twitter-oauth": TwitterOAuthServiceSchema.default(
|
|
141
157
|
TwitterOAuthServiceSchema.parse({}),
|
|
142
158
|
),
|
|
159
|
+
"asana-oauth": AsanaOAuthServiceSchema.default(
|
|
160
|
+
AsanaOAuthServiceSchema.parse({}),
|
|
161
|
+
),
|
|
162
|
+
"todoist-oauth": TodoistOAuthServiceSchema.default(
|
|
163
|
+
TodoistOAuthServiceSchema.parse({}),
|
|
164
|
+
),
|
|
165
|
+
"discord-oauth": DiscordOAuthServiceSchema.default(
|
|
166
|
+
DiscordOAuthServiceSchema.parse({}),
|
|
167
|
+
),
|
|
168
|
+
"hubspot-oauth": HubspotOAuthServiceSchema.default(
|
|
169
|
+
HubspotOAuthServiceSchema.parse({}),
|
|
170
|
+
),
|
|
143
171
|
meet: MeetDaemonServiceSchema.default(MeetDaemonServiceSchema.parse({})),
|
|
144
172
|
});
|
|
145
173
|
export type Services = z.infer<typeof ServicesSchema>;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
|
|
2
|
+
import { resolveModelIntent } from "../providers/model-intents.js";
|
|
3
|
+
import type { ModelIntent } from "../providers/types.js";
|
|
1
4
|
import { loadRawConfig, saveRawConfig } from "./loader.js";
|
|
2
5
|
import {
|
|
3
6
|
DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
|
|
@@ -5,41 +8,82 @@ import {
|
|
|
5
8
|
} from "./schemas/llm.js";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* updates propagate automatically. User-created profiles (keyed by
|
|
12
|
-
* different names) are never touched.
|
|
11
|
+
* Template for a daemon-managed inference profile. The profile's model is
|
|
12
|
+
* resolved at seed time from `PROVIDER_MODEL_INTENTS` so the catalog stays the
|
|
13
|
+
* single source of truth for "which model does this intent map to?".
|
|
13
14
|
*/
|
|
14
|
-
|
|
15
|
+
type ManagedProfileTemplate = Omit<ProfileEntry, "provider" | "model"> & {
|
|
16
|
+
intent: ModelIntent;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Anthropic-managed profiles. Always seeded so users can target Anthropic via
|
|
21
|
+
* their own key, even when the resolved default provider is something else.
|
|
22
|
+
*/
|
|
23
|
+
const ANTHROPIC_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
|
|
15
24
|
balanced: {
|
|
25
|
+
intent: "balanced",
|
|
16
26
|
source: "managed",
|
|
17
27
|
label: "Balanced",
|
|
18
28
|
description: "Good balance of quality, cost, and speed",
|
|
19
|
-
provider: "anthropic",
|
|
20
|
-
model: "claude-sonnet-4-6",
|
|
21
29
|
maxTokens: 16000,
|
|
22
30
|
effort: "high",
|
|
23
31
|
thinking: { enabled: true, streamThinking: true },
|
|
24
32
|
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
25
33
|
},
|
|
26
34
|
"quality-optimized": {
|
|
35
|
+
intent: "quality-optimized",
|
|
27
36
|
source: "managed",
|
|
28
37
|
label: "Quality",
|
|
29
38
|
description: "Best results with the most capable model",
|
|
30
|
-
provider: "anthropic",
|
|
31
|
-
model: "claude-opus-4-7",
|
|
32
39
|
maxTokens: 32000,
|
|
33
40
|
effort: "max",
|
|
34
41
|
thinking: { enabled: true, streamThinking: true },
|
|
35
42
|
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
36
43
|
},
|
|
37
44
|
"cost-optimized": {
|
|
45
|
+
intent: "latency-optimized",
|
|
38
46
|
source: "managed",
|
|
39
47
|
label: "Speed",
|
|
40
48
|
description: "Fastest responses at lower cost",
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
maxTokens: 8192,
|
|
50
|
+
effort: "low",
|
|
51
|
+
thinking: { enabled: false, streamThinking: false },
|
|
52
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Custom-provider profile templates. Materialized at seed time when the
|
|
58
|
+
* resolved default provider is non-Anthropic, using `resolveModelIntent` to
|
|
59
|
+
* pick the model.
|
|
60
|
+
*/
|
|
61
|
+
const CUSTOM_PROFILE_TEMPLATES: Record<string, ManagedProfileTemplate> = {
|
|
62
|
+
"custom-balanced": {
|
|
63
|
+
intent: "balanced",
|
|
64
|
+
source: "user",
|
|
65
|
+
label: "Balanced (Custom Provider)",
|
|
66
|
+
description: "Good balance of quality, cost, and speed",
|
|
67
|
+
maxTokens: 16000,
|
|
68
|
+
effort: "high",
|
|
69
|
+
thinking: { enabled: true, streamThinking: true },
|
|
70
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
71
|
+
},
|
|
72
|
+
"custom-quality-optimized": {
|
|
73
|
+
intent: "quality-optimized",
|
|
74
|
+
source: "user",
|
|
75
|
+
label: "Quality (Custom Provider)",
|
|
76
|
+
description: "Best results with the most capable model",
|
|
77
|
+
maxTokens: 32000,
|
|
78
|
+
effort: "max",
|
|
79
|
+
thinking: { enabled: true, streamThinking: true },
|
|
80
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
81
|
+
},
|
|
82
|
+
"custom-cost-optimized": {
|
|
83
|
+
intent: "latency-optimized",
|
|
84
|
+
source: "user",
|
|
85
|
+
label: "Speed (Custom Provider)",
|
|
86
|
+
description: "Fastest responses at lower cost",
|
|
43
87
|
maxTokens: 8192,
|
|
44
88
|
effort: "low",
|
|
45
89
|
thinking: { enabled: false, streamThinking: false },
|
|
@@ -48,26 +92,47 @@ const MANAGED_PROFILE_SEED_DATA: Record<string, ProfileEntry> = {
|
|
|
48
92
|
};
|
|
49
93
|
|
|
50
94
|
export const MANAGED_PROFILE_NAMES = new Set(
|
|
51
|
-
Object.keys(
|
|
95
|
+
Object.keys(ANTHROPIC_PROFILE_TEMPLATES),
|
|
52
96
|
);
|
|
53
97
|
|
|
98
|
+
const SEEDED_PROFILE_NAMES = new Set([
|
|
99
|
+
...Object.keys(ANTHROPIC_PROFILE_TEMPLATES),
|
|
100
|
+
...Object.keys(CUSTOM_PROFILE_TEMPLATES),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const KNOWN_PROVIDERS = new Set(PROVIDER_CATALOG.map((entry) => entry.id));
|
|
104
|
+
|
|
105
|
+
export type SeedInferenceProfilesOptions = {
|
|
106
|
+
/**
|
|
107
|
+
* Managed profile names supplied by the platform/default overlay for this
|
|
108
|
+
* startup. Those entries are already on disk by the time seeding runs and
|
|
109
|
+
* should remain authoritative for this boot.
|
|
110
|
+
*/
|
|
111
|
+
preserveProfileNames?: Iterable<string>;
|
|
112
|
+
preserveActiveProfile?: boolean;
|
|
113
|
+
};
|
|
114
|
+
|
|
54
115
|
/**
|
|
55
116
|
* Seed managed inference profiles into the workspace config.
|
|
56
117
|
*
|
|
57
|
-
* Called on every daemon startup after workspace migrations and
|
|
58
|
-
* the first `loadConfig()`.
|
|
59
|
-
* (
|
|
60
|
-
*
|
|
61
|
-
*
|
|
118
|
+
* Called on every daemon startup after workspace migrations and default-config
|
|
119
|
+
* overlay merge, but before the first `loadConfig()`. The 3 Anthropic-managed
|
|
120
|
+
* profiles (`balanced`, `quality-optimized`, `cost-optimized`) are always
|
|
121
|
+
* written so users can target Anthropic via their own key. When the resolved
|
|
122
|
+
* default provider is non-Anthropic, the 3 `custom-*` profiles are also
|
|
123
|
+
* materialized using `resolveModelIntent` against that provider, and
|
|
124
|
+
* `custom-balanced` becomes the active profile for fresh hatches.
|
|
62
125
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
126
|
+
* Default-config overlays can provide their own profile fragments and active
|
|
127
|
+
* profile. Lifecycle passes those explicit fields in `options`, letting local
|
|
128
|
+
* hatches still receive managed defaults while platform-owned profile choices
|
|
129
|
+
* remain authoritative.
|
|
66
130
|
*/
|
|
67
|
-
export function seedInferenceProfiles(
|
|
68
|
-
|
|
69
|
-
|
|
131
|
+
export function seedInferenceProfiles(
|
|
132
|
+
options: SeedInferenceProfilesOptions = {},
|
|
133
|
+
): void {
|
|
70
134
|
const config = loadRawConfig();
|
|
135
|
+
const preservedProfileNames = new Set(options.preserveProfileNames ?? []);
|
|
71
136
|
|
|
72
137
|
if (config.llm == null || typeof config.llm !== "object") {
|
|
73
138
|
config.llm = {};
|
|
@@ -79,25 +144,135 @@ export function seedInferenceProfiles(): void {
|
|
|
79
144
|
}
|
|
80
145
|
const profiles = llm.profiles as Record<string, Record<string, unknown>>;
|
|
81
146
|
|
|
82
|
-
|
|
83
|
-
|
|
147
|
+
const requestedProvider =
|
|
148
|
+
readString(readObject(llm.default)?.provider) ?? "anthropic";
|
|
149
|
+
const isKnownProvider = KNOWN_PROVIDERS.has(requestedProvider);
|
|
150
|
+
const resolvedProvider: NonNullable<ProfileEntry["provider"]> =
|
|
151
|
+
isKnownProvider
|
|
152
|
+
? (requestedProvider as NonNullable<ProfileEntry["provider"]>)
|
|
153
|
+
: "anthropic";
|
|
154
|
+
const isAnthropicDefault = resolvedProvider === "anthropic";
|
|
155
|
+
|
|
156
|
+
// Persist the resolved provider when the overlay supplied an unrecognized
|
|
157
|
+
// value, so the on-disk config doesn't keep emitting Zod warnings for
|
|
158
|
+
// `unknownprov` on every load.
|
|
159
|
+
if (!isKnownProvider) {
|
|
160
|
+
const defaultBlock = (readObject(llm.default) ?? {}) as Record<
|
|
161
|
+
string,
|
|
162
|
+
unknown
|
|
163
|
+
>;
|
|
164
|
+
defaultBlock.provider = resolvedProvider;
|
|
165
|
+
llm.default = defaultBlock;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const [name, template] of Object.entries(ANTHROPIC_PROFILE_TEMPLATES)) {
|
|
169
|
+
if (preservedProfileNames.has(name)) continue;
|
|
170
|
+
// Preserve a previously overlay-supplied profile whose provider matches
|
|
171
|
+
// the resolved default — that's the platform-managed case where the
|
|
172
|
+
// overlay file has already been consumed and archived. Only valid for
|
|
173
|
+
// non-Anthropic resolutions; when the default is Anthropic the daemon
|
|
174
|
+
// owns these names and re-seeds with Anthropic data so a stale openai
|
|
175
|
+
// `balanced` doesn't keep routing through the wrong provider after a
|
|
176
|
+
// re-hatch back to Anthropic.
|
|
177
|
+
const existing = readObject(profiles[name]);
|
|
178
|
+
const existingProvider = readString(existing?.provider);
|
|
179
|
+
if (
|
|
180
|
+
existing !== null &&
|
|
181
|
+
!isAnthropicDefault &&
|
|
182
|
+
existingProvider === resolvedProvider
|
|
183
|
+
) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
profiles[name] = materializeProfile(template, "anthropic");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!isAnthropicDefault) {
|
|
190
|
+
for (const [name, template] of Object.entries(CUSTOM_PROFILE_TEMPLATES)) {
|
|
191
|
+
if (preservedProfileNames.has(name)) continue;
|
|
192
|
+
if (readObject(profiles[name]) !== null) continue;
|
|
193
|
+
profiles[name] = materializeProfile(template, resolvedProvider);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const requestedActiveProfile = readString(llm.activeProfile);
|
|
198
|
+
const requestedActiveEntry =
|
|
199
|
+
requestedActiveProfile !== undefined
|
|
200
|
+
? readObject(profiles[requestedActiveProfile])
|
|
201
|
+
: null;
|
|
202
|
+
const requestedActiveExists = requestedActiveEntry !== null;
|
|
203
|
+
const shouldPreserveActiveProfile =
|
|
204
|
+
options.preserveActiveProfile === true && requestedActiveExists;
|
|
205
|
+
|
|
206
|
+
// Decide whether the existing active profile is still appropriate. A managed
|
|
207
|
+
// profile whose provider no longer matches the resolved default goes stale
|
|
208
|
+
// (e.g. re-hatching anthropic→openai leaves `balanced` pointing at anthropic;
|
|
209
|
+
// re-hatching openai→anthropic leaves `custom-balanced` pointing at openai).
|
|
210
|
+
// Either direction should land the user on the new default's `balanced`
|
|
211
|
+
// counterpart rather than routing the main agent to a stale provider.
|
|
212
|
+
// User-created profiles are left alone — those are the user's choice.
|
|
213
|
+
let keepActiveProfile = shouldPreserveActiveProfile;
|
|
214
|
+
if (!keepActiveProfile && requestedActiveExists) {
|
|
215
|
+
const isSeededName = SEEDED_PROFILE_NAMES.has(requestedActiveProfile!);
|
|
216
|
+
const activeProvider = readString(requestedActiveEntry?.provider);
|
|
217
|
+
const managedActiveProviderMismatch =
|
|
218
|
+
isSeededName && activeProvider !== resolvedProvider;
|
|
219
|
+
keepActiveProfile = !managedActiveProviderMismatch;
|
|
84
220
|
}
|
|
85
221
|
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
llm.activeProfile =
|
|
222
|
+
let activeProfileName: string;
|
|
223
|
+
if (keepActiveProfile) {
|
|
224
|
+
activeProfileName = requestedActiveProfile!;
|
|
225
|
+
} else {
|
|
226
|
+
activeProfileName = isAnthropicDefault ? "balanced" : "custom-balanced";
|
|
227
|
+
llm.activeProfile = activeProfileName;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Sync `llm.default.model` to the active profile's model so the providers
|
|
231
|
+
// registry sees a coherent provider/model pair. Only writes when the on-disk
|
|
232
|
+
// default model is missing or unambiguously belongs to a *different*
|
|
233
|
+
// provider's catalog (e.g. `claude-opus-4-7` paired with `provider: openai`).
|
|
234
|
+
// A user-supplied model not listed in any provider's catalog is preserved —
|
|
235
|
+
// ollama and openrouter expose far more models than `PROVIDER_CATALOG` lists,
|
|
236
|
+
// and silently overwriting `codellama`/`phi3`/etc. on every restart would
|
|
237
|
+
// break those users' configs. Skipped when the overlay owns the active
|
|
238
|
+
// profile (platform mode).
|
|
239
|
+
if (!shouldPreserveActiveProfile) {
|
|
240
|
+
const activeEntry = readObject(profiles[activeProfileName]);
|
|
241
|
+
const activeModel = readString(activeEntry?.model);
|
|
242
|
+
if (activeModel !== undefined) {
|
|
243
|
+
const defaultBlock = (readObject(llm.default) ?? {}) as Record<
|
|
244
|
+
string,
|
|
245
|
+
unknown
|
|
246
|
+
>;
|
|
247
|
+
const currentModel = readString(defaultBlock.model);
|
|
248
|
+
const modelBelongsToOtherProvider =
|
|
249
|
+
currentModel !== undefined &&
|
|
250
|
+
PROVIDER_CATALOG.some(
|
|
251
|
+
(p) =>
|
|
252
|
+
p.id !== resolvedProvider &&
|
|
253
|
+
p.models.some((m) => m.id === currentModel),
|
|
254
|
+
);
|
|
255
|
+
const shouldOverwriteDefaultModel =
|
|
256
|
+
currentModel === undefined || modelBelongsToOtherProvider;
|
|
257
|
+
if (shouldOverwriteDefaultModel) {
|
|
258
|
+
defaultBlock.model = activeModel;
|
|
259
|
+
llm.default = defaultBlock;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
92
262
|
}
|
|
93
263
|
|
|
94
264
|
const profileOrder = Array.isArray(llm.profileOrder)
|
|
95
265
|
? (llm.profileOrder as string[])
|
|
96
266
|
: [];
|
|
97
267
|
const orderSet = new Set(profileOrder);
|
|
98
|
-
|
|
268
|
+
const seededOrder = [
|
|
269
|
+
...Object.keys(ANTHROPIC_PROFILE_TEMPLATES),
|
|
270
|
+
...(isAnthropicDefault ? [] : Object.keys(CUSTOM_PROFILE_TEMPLATES)),
|
|
271
|
+
];
|
|
272
|
+
for (const name of seededOrder) {
|
|
99
273
|
if (!orderSet.has(name)) {
|
|
100
274
|
profileOrder.push(name);
|
|
275
|
+
orderSet.add(name);
|
|
101
276
|
}
|
|
102
277
|
}
|
|
103
278
|
llm.profileOrder = profileOrder;
|
|
@@ -115,3 +290,25 @@ export function seedInferenceProfiles(): void {
|
|
|
115
290
|
|
|
116
291
|
saveRawConfig(config);
|
|
117
292
|
}
|
|
293
|
+
|
|
294
|
+
function materializeProfile(
|
|
295
|
+
template: ManagedProfileTemplate,
|
|
296
|
+
provider: NonNullable<ProfileEntry["provider"]>,
|
|
297
|
+
): ProfileEntry {
|
|
298
|
+
const { intent, ...rest } = template;
|
|
299
|
+
return {
|
|
300
|
+
...rest,
|
|
301
|
+
provider,
|
|
302
|
+
model: resolveModelIntent(provider, intent),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
307
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
308
|
+
? (value as Record<string, unknown>)
|
|
309
|
+
: null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function readString(value: unknown): string | undefined {
|
|
313
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
314
|
+
}
|
|
@@ -704,31 +704,6 @@ export function mergeContacts(
|
|
|
704
704
|
return getContactInternal(keepId)!;
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
-
/**
|
|
708
|
-
* Delete a contact by ID. Guardians cannot be deleted as a safety guard.
|
|
709
|
-
* Associated contactChannels and assistantContactMetadata rows are
|
|
710
|
-
* cascade-deleted by the DB schema's onDelete constraints.
|
|
711
|
-
*/
|
|
712
|
-
export function deleteContact(
|
|
713
|
-
contactId: string,
|
|
714
|
-
): "ok" | "not_found" | "is_guardian" {
|
|
715
|
-
const db = getDb();
|
|
716
|
-
|
|
717
|
-
const contact = db
|
|
718
|
-
.select()
|
|
719
|
-
.from(contacts)
|
|
720
|
-
.where(eq(contacts.id, contactId))
|
|
721
|
-
.get();
|
|
722
|
-
|
|
723
|
-
if (!contact) return "not_found";
|
|
724
|
-
if (contact.role === "guardian") return "is_guardian";
|
|
725
|
-
|
|
726
|
-
db.delete(contacts).where(eq(contacts.id, contactId)).run();
|
|
727
|
-
|
|
728
|
-
emitContactChange();
|
|
729
|
-
return "ok";
|
|
730
|
-
}
|
|
731
|
-
|
|
732
707
|
/**
|
|
733
708
|
* Find a contact by a specific channel address. Returns null if not found.
|
|
734
709
|
*/
|
|
@@ -56,6 +56,16 @@ let autoAnalyzeEnabled = true;
|
|
|
56
56
|
// `disposeConversation` must skip the `graph_extract` enqueue.
|
|
57
57
|
const autoAnalysisConversations = new Set<string>();
|
|
58
58
|
|
|
59
|
+
// Toggles the `memory.v2.enabled` flag the disposal code reads via
|
|
60
|
+
// `getConfig()`. Defaults to false so the bulk of the suite — which asserts
|
|
61
|
+
// v1 graph_extract still fires — keeps its semantics. The dedicated v2 cases
|
|
62
|
+
// flip this to true.
|
|
63
|
+
let v2Enabled = false;
|
|
64
|
+
|
|
65
|
+
mock.module("../../config/loader.js", () => ({
|
|
66
|
+
getConfig: () => ({ memory: { v2: { enabled: v2Enabled } } }),
|
|
67
|
+
}));
|
|
68
|
+
|
|
59
69
|
mock.module("../../memory/auto-analysis-guard.js", () => ({
|
|
60
70
|
AUTO_ANALYSIS_SOURCE: "auto-analysis",
|
|
61
71
|
isAutoAnalysisConversation: (conversationId: string) =>
|
|
@@ -160,6 +170,7 @@ describe("disposeConversation — auto-analysis enqueue", () => {
|
|
|
160
170
|
autoAnalyzeCalls.length = 0;
|
|
161
171
|
autoAnalyzeEnabled = true;
|
|
162
172
|
autoAnalysisConversations.clear();
|
|
173
|
+
v2Enabled = false;
|
|
163
174
|
});
|
|
164
175
|
|
|
165
176
|
test("guardian conversation with auto-analyze ON — enqueues both graph_extract and conversation_analyze (via helper)", () => {
|
|
@@ -313,4 +324,25 @@ describe("disposeConversation — auto-analysis enqueue", () => {
|
|
|
313
324
|
}));
|
|
314
325
|
autoAnalyzeEnabled = originalEnabled;
|
|
315
326
|
});
|
|
327
|
+
|
|
328
|
+
test("memory v2 enabled — graph_extract enqueue is suppressed (auto-analysis still runs)", () => {
|
|
329
|
+
// Under memory v2, the v1 graph has no readers (retrieval is bypassed at
|
|
330
|
+
// conversation-graph-memory.ts), so producing extraction jobs just fills
|
|
331
|
+
// the queue with stale work. Auto-analysis is orthogonal and must keep
|
|
332
|
+
// running.
|
|
333
|
+
v2Enabled = true;
|
|
334
|
+
const ctx = makeDisposeContext({
|
|
335
|
+
conversationId: "conv-v2-on",
|
|
336
|
+
trustClass: "guardian",
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
disposeConversation(ctx);
|
|
340
|
+
|
|
341
|
+
expect(memoryJobCalls).toHaveLength(0);
|
|
342
|
+
expect(autoAnalyzeCalls).toHaveLength(1);
|
|
343
|
+
expect(autoAnalyzeCalls[0]).toEqual({
|
|
344
|
+
conversationId: "conv-v2-on",
|
|
345
|
+
trigger: "lifecycle",
|
|
346
|
+
});
|
|
347
|
+
});
|
|
316
348
|
});
|