@vellumai/assistant 0.8.3 → 0.8.4
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/docker-entrypoint.sh +0 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +610 -16
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +25 -7
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-resolver.test.ts +85 -1
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/openai-provider.test.ts +105 -0
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +51 -28
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +57 -20
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +19 -0
- package/src/config/feature-flag-registry.json +99 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-v2.ts +48 -1
- package/src/context/compactor.ts +8 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
- package/src/daemon/conversation-agent-loop.ts +223 -54
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +135 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -5
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +79 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/db-init.ts +16 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -2
- package/src/memory/memory-retrospective-job.ts +408 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +25 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +31 -14
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +21 -10
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +9 -1
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +82 -9
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -68
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +8 -0
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +53 -3
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +15 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/model-catalog.ts +48 -1
- package/src/providers/openai/chat-completions-provider.ts +57 -20
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +151 -56
- package/src/runtime/auth/route-policy.ts +7 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -4
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +60 -1
- package/src/runtime/routes/conversation-routes.ts +281 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +12 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +4 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -28,8 +28,14 @@ import {
|
|
|
28
28
|
getConnection,
|
|
29
29
|
listConnections,
|
|
30
30
|
MANAGED_CONNECTION_NAMES,
|
|
31
|
+
PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
|
|
31
32
|
updateConnection,
|
|
32
33
|
} from "../../providers/inference/connections.js";
|
|
34
|
+
import {
|
|
35
|
+
isPrivateOrLocalHost,
|
|
36
|
+
resolveHostAddresses,
|
|
37
|
+
resolveRequestAddress,
|
|
38
|
+
} from "../../tools/network/url-safety.js";
|
|
33
39
|
import { BadRequestError, ConflictError, NotFoundError } from "./errors.js";
|
|
34
40
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
35
41
|
|
|
@@ -59,10 +65,26 @@ function rejectDisabledOpenAICompatibleProvider(provider: string): void {
|
|
|
59
65
|
// Custom provider field parsing (openai-compatible base_url + models)
|
|
60
66
|
// ---------------------------------------------------------------------------
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Parse and validate `base_url` and `models` from the request body.
|
|
70
|
+
*
|
|
71
|
+
* `base_url` is only accepted for providers in
|
|
72
|
+
* `PROVIDERS_REQUIRING_BASE_URL_AND_MODELS` (currently `openai-compatible`).
|
|
73
|
+
* For all other providers, supplying `base_url` returns a 400. This prevents
|
|
74
|
+
* API-key exfiltration: an attacker cannot create an `anthropic` connection
|
|
75
|
+
* with a `base_url` pointing to their own server, which would redirect all
|
|
76
|
+
* LLM calls (and the API key) to the attacker.
|
|
77
|
+
*
|
|
78
|
+
* Even for `openai-compatible`, the `base_url` must not point to private
|
|
79
|
+
* networks or cloud metadata endpoints (SSRF protection).
|
|
80
|
+
*/
|
|
81
|
+
async function parseCustomProviderFields(
|
|
82
|
+
body: Record<string, unknown>,
|
|
83
|
+
provider: string,
|
|
84
|
+
): Promise<{
|
|
63
85
|
baseUrl?: string | null;
|
|
64
86
|
models?: ConnectionModel[] | null;
|
|
65
|
-
} {
|
|
87
|
+
}> {
|
|
66
88
|
const out: {
|
|
67
89
|
baseUrl?: string | null;
|
|
68
90
|
models?: ConnectionModel[] | null;
|
|
@@ -70,11 +92,24 @@ function parseCustomProviderFields(body: Record<string, unknown>): {
|
|
|
70
92
|
|
|
71
93
|
if ("base_url" in body) {
|
|
72
94
|
const raw = body.base_url;
|
|
95
|
+
|
|
96
|
+
// Gate: base_url is only valid for openai-compatible providers.
|
|
97
|
+
if (
|
|
98
|
+
raw !== null &&
|
|
99
|
+
raw !== undefined &&
|
|
100
|
+
!PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)
|
|
101
|
+
) {
|
|
102
|
+
throw new BadRequestError(
|
|
103
|
+
`base_url is only valid for openai-compatible providers. Remove base_url or use the openai-compatible provider type.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
73
107
|
if (raw === null) {
|
|
74
108
|
out.baseUrl = null;
|
|
75
109
|
} else if (typeof raw === "string" && raw.length > 0) {
|
|
110
|
+
let parsed: URL;
|
|
76
111
|
try {
|
|
77
|
-
|
|
112
|
+
parsed = new URL(raw);
|
|
78
113
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
79
114
|
throw new BadRequestError(`Invalid base_url: must be an http(s) URL`);
|
|
80
115
|
}
|
|
@@ -84,6 +119,27 @@ function parseCustomProviderFields(body: Record<string, unknown>): {
|
|
|
84
119
|
`Invalid base_url: must be a valid http(s) URL`,
|
|
85
120
|
);
|
|
86
121
|
}
|
|
122
|
+
|
|
123
|
+
// SSRF protection: reject private IPs, localhost, cloud metadata endpoints.
|
|
124
|
+
const hostname = parsed.hostname;
|
|
125
|
+
if (isPrivateOrLocalHost(hostname)) {
|
|
126
|
+
throw new BadRequestError(
|
|
127
|
+
`Invalid base_url: must not point to a private or local network address.`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// DNS resolution check: hostname may resolve to a private IP.
|
|
132
|
+
const resolved = await resolveRequestAddress(
|
|
133
|
+
hostname,
|
|
134
|
+
resolveHostAddresses,
|
|
135
|
+
/* allowPrivateNetwork */ false,
|
|
136
|
+
);
|
|
137
|
+
if (resolved.blockedAddress) {
|
|
138
|
+
throw new BadRequestError(
|
|
139
|
+
`Invalid base_url: hostname resolves to a private network address.`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
87
143
|
out.baseUrl = raw;
|
|
88
144
|
} else {
|
|
89
145
|
throw new BadRequestError(
|
|
@@ -145,7 +201,7 @@ function handleGetConnection({ pathParams = {} }: RouteHandlerArgs) {
|
|
|
145
201
|
return conn;
|
|
146
202
|
}
|
|
147
203
|
|
|
148
|
-
function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
204
|
+
async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
149
205
|
const name = body.name;
|
|
150
206
|
const provider = body.provider;
|
|
151
207
|
const auth = body.auth;
|
|
@@ -186,7 +242,7 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
186
242
|
);
|
|
187
243
|
}
|
|
188
244
|
|
|
189
|
-
const customFields = parseCustomProviderFields(body);
|
|
245
|
+
const customFields = await parseCustomProviderFields(body, providerResult.data);
|
|
190
246
|
|
|
191
247
|
const result = createConnection(getDb(), {
|
|
192
248
|
name,
|
|
@@ -224,7 +280,7 @@ function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
|
|
|
224
280
|
return result.connection;
|
|
225
281
|
}
|
|
226
282
|
|
|
227
|
-
function handleUpdateConnection({
|
|
283
|
+
async function handleUpdateConnection({
|
|
228
284
|
pathParams = {},
|
|
229
285
|
body = {},
|
|
230
286
|
}: RouteHandlerArgs) {
|
|
@@ -278,7 +334,7 @@ function handleUpdateConnection({
|
|
|
278
334
|
);
|
|
279
335
|
}
|
|
280
336
|
|
|
281
|
-
const customFields = parseCustomProviderFields(body);
|
|
337
|
+
const customFields = await parseCustomProviderFields(body, existing.provider);
|
|
282
338
|
|
|
283
339
|
const result = updateConnection(getDb(), name, {
|
|
284
340
|
auth: authResult.data,
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* POST /v1/integrations/a2a/invite — create a shareable A2A invite token
|
|
8
8
|
* POST /v1/integrations/a2a/invite/complete — sender-side invite completion
|
|
9
9
|
* POST /v1/integrations/a2a/invite/redeem — receiver-side invite redemption
|
|
10
|
+
* POST /v1/integrations/a2a/invite/accept — self-hosted broker: orchestrate complete + redeem
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { isA2AEnabled } from "../../../a2a/feature-gate.js";
|
|
13
14
|
import { getConfig } from "../../../config/loader.js";
|
|
14
15
|
import {
|
|
16
|
+
acceptA2AInvite,
|
|
15
17
|
clearA2AConfig,
|
|
16
18
|
completeA2AInvite,
|
|
17
19
|
createA2AInvite,
|
|
@@ -19,7 +21,7 @@ import {
|
|
|
19
21
|
redeemA2AInvite,
|
|
20
22
|
setA2AConfig,
|
|
21
23
|
} from "../../../daemon/handlers/config-a2a.js";
|
|
22
|
-
import { BadRequestError } from "../errors.js";
|
|
24
|
+
import { BadGatewayError, BadRequestError } from "../errors.js";
|
|
23
25
|
import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
|
|
24
26
|
|
|
25
27
|
// ---------------------------------------------------------------------------
|
|
@@ -164,6 +166,52 @@ function handleRedeemA2AInvite({ body = {} }: RouteHandlerArgs) {
|
|
|
164
166
|
return result;
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
async function handleAcceptA2AInvite({ body = {} }: RouteHandlerArgs) {
|
|
170
|
+
assertA2AFlag();
|
|
171
|
+
const { senderGatewayUrl, senderAssistantId, token } = body as {
|
|
172
|
+
senderGatewayUrl?: unknown;
|
|
173
|
+
senderAssistantId?: unknown;
|
|
174
|
+
token?: unknown;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (typeof senderGatewayUrl !== "string" || !senderGatewayUrl) {
|
|
178
|
+
throw new BadRequestError(
|
|
179
|
+
"senderGatewayUrl is required and must be a non-empty string",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
new URL(senderGatewayUrl);
|
|
184
|
+
} catch {
|
|
185
|
+
throw new BadRequestError("senderGatewayUrl must be a valid URL");
|
|
186
|
+
}
|
|
187
|
+
if (typeof senderAssistantId !== "string" || !senderAssistantId) {
|
|
188
|
+
throw new BadRequestError(
|
|
189
|
+
"senderAssistantId is required and must be a non-empty string",
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (typeof token !== "string" || !token) {
|
|
193
|
+
throw new BadRequestError(
|
|
194
|
+
"token is required and must be a non-empty string",
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const result = await acceptA2AInvite({
|
|
199
|
+
senderGatewayUrl,
|
|
200
|
+
senderAssistantId,
|
|
201
|
+
token,
|
|
202
|
+
});
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
const isSenderFault =
|
|
205
|
+
result.errorCode === "sender_unreachable" ||
|
|
206
|
+
result.errorCode === "complete_failed";
|
|
207
|
+
if (isSenderFault) {
|
|
208
|
+
throw new BadGatewayError(result.error ?? "Failed to accept A2A invite");
|
|
209
|
+
}
|
|
210
|
+
throw new BadRequestError(result.error ?? "Failed to accept A2A invite");
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
167
215
|
// ---------------------------------------------------------------------------
|
|
168
216
|
// Route definitions
|
|
169
217
|
// ---------------------------------------------------------------------------
|
|
@@ -232,4 +280,15 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
232
280
|
requirePolicyEnforcement: true,
|
|
233
281
|
handler: handleRedeemA2AInvite,
|
|
234
282
|
},
|
|
283
|
+
{
|
|
284
|
+
operationId: "integrations_a2a_invite_accept_post",
|
|
285
|
+
endpoint: "integrations/a2a/invite/accept",
|
|
286
|
+
method: "POST",
|
|
287
|
+
summary: "Accept A2A invite (self-hosted broker)",
|
|
288
|
+
description:
|
|
289
|
+
"Orchestrate cross-daemon invite acceptance for self-hosted deployments. Calls the sender's invite/complete, then creates a local contact via invite/redeem.",
|
|
290
|
+
tags: ["integrations"],
|
|
291
|
+
requirePolicyEnforcement: true,
|
|
292
|
+
handler: handleAcceptA2AInvite,
|
|
293
|
+
},
|
|
235
294
|
];
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
getWorkspaceConfigPath,
|
|
37
37
|
} from "../../util/platform.js";
|
|
38
38
|
import { APP_VERSION, COMMIT_SHA } from "../../version.js";
|
|
39
|
+
import { assistantEventHub } from "../assistant-event-hub.js";
|
|
39
40
|
import { createTarGz } from "./archive-utils.js";
|
|
40
41
|
import { InternalError } from "./errors.js";
|
|
41
42
|
import { collectWorkspaceData } from "./log-export/workspace-allowlist.js";
|
|
@@ -270,6 +271,44 @@ async function handleExport({
|
|
|
270
271
|
);
|
|
271
272
|
}
|
|
272
273
|
|
|
274
|
+
// --- Connected clients (`assistant clients list --json`) ---
|
|
275
|
+
try {
|
|
276
|
+
const clients = assistantEventHub.listClients();
|
|
277
|
+
const clientsList = {
|
|
278
|
+
clients: clients.map((c) => ({
|
|
279
|
+
clientId: c.clientId,
|
|
280
|
+
interfaceId: c.interfaceId,
|
|
281
|
+
capabilities: c.capabilities,
|
|
282
|
+
machineName: c.machineName,
|
|
283
|
+
connectedAt: c.connectedAt.toISOString(),
|
|
284
|
+
lastActiveAt: c.lastActiveAt.toISOString(),
|
|
285
|
+
})),
|
|
286
|
+
};
|
|
287
|
+
writeFileSync(
|
|
288
|
+
join(staging, "clients-list.json"),
|
|
289
|
+
JSON.stringify(clientsList, null, 2),
|
|
290
|
+
"utf-8",
|
|
291
|
+
);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
294
|
+
writeFileSync(
|
|
295
|
+
join(staging, "clients-list-error.json"),
|
|
296
|
+
JSON.stringify(
|
|
297
|
+
{
|
|
298
|
+
error: message,
|
|
299
|
+
collectedAt: new Date().toISOString(),
|
|
300
|
+
},
|
|
301
|
+
null,
|
|
302
|
+
2,
|
|
303
|
+
),
|
|
304
|
+
"utf-8",
|
|
305
|
+
);
|
|
306
|
+
log.warn(
|
|
307
|
+
{ err },
|
|
308
|
+
"Failed to collect connected clients list, continuing without it",
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
273
312
|
// --- Export manifest ---
|
|
274
313
|
const manifestType = conversationId
|
|
275
314
|
? ("conversation-export" as const)
|
|
@@ -8,6 +8,8 @@ import { join } from "node:path";
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
import { loadConfig } from "../../config/loader.js";
|
|
11
|
+
import type { AssistantConfig } from "../../config/types.js";
|
|
12
|
+
import { getDb } from "../../memory/db-connection.js";
|
|
11
13
|
import {
|
|
12
14
|
enqueueMemoryJob,
|
|
13
15
|
type MemoryJobType,
|
|
@@ -21,12 +23,16 @@ import {
|
|
|
21
23
|
totalEdgeCount,
|
|
22
24
|
validateEdgeTargets,
|
|
23
25
|
} from "../../memory/v2/edge-index.js";
|
|
26
|
+
import { computeInjectionScores } from "../../memory/v2/injection-events.js";
|
|
27
|
+
import { loadNowText } from "../../memory/v2/now-text.js";
|
|
28
|
+
import { getPageIndex } from "../../memory/v2/page-index.js";
|
|
24
29
|
import {
|
|
25
30
|
getConceptsDir,
|
|
26
31
|
listPages,
|
|
27
32
|
readPage,
|
|
28
33
|
renderPageContent,
|
|
29
34
|
} from "../../memory/v2/page-store.js";
|
|
35
|
+
import { type RouterSource, runRouter } from "../../memory/v2/router.js";
|
|
30
36
|
import { seedV2SkillEntries } from "../../memory/v2/skill-store.js";
|
|
31
37
|
import { getLogger } from "../../util/logger.js";
|
|
32
38
|
import { getWorkspaceDir } from "../../util/platform.js";
|
|
@@ -290,6 +296,195 @@ async function handleConceptFrequency({
|
|
|
290
296
|
return getConceptFrequencySummary(workspaceDir, { conversationId, sinceMs });
|
|
291
297
|
}
|
|
292
298
|
|
|
299
|
+
// ── EMA scores ──────────────────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
const MemoryV2EmaScoresParams = z.object({}).strict();
|
|
302
|
+
|
|
303
|
+
export interface MemoryV2EmaScoresEntry {
|
|
304
|
+
slug: string;
|
|
305
|
+
/** Time-decayed injection frequency; 0 when no events in the read window. */
|
|
306
|
+
score: number;
|
|
307
|
+
/** File mtime in epoch ms; 0 for synthetic entries (skills, CLI commands). */
|
|
308
|
+
modifiedAt: number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface MemoryV2EmaScoresResult {
|
|
312
|
+
/** Every page index entry, sorted by score descending then slug ASCII. */
|
|
313
|
+
entries: MemoryV2EmaScoresEntry[];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function handleEmaScores({
|
|
317
|
+
body = {},
|
|
318
|
+
}: RouteHandlerArgs): Promise<MemoryV2EmaScoresResult> {
|
|
319
|
+
// Intentionally NOT gated on `memory.v2.enabled` — operators inspecting
|
|
320
|
+
// EMA data before flipping tier-2 routing on is a legitimate dry-run
|
|
321
|
+
// use case, mirroring `memory_v2_validate`.
|
|
322
|
+
MemoryV2EmaScoresParams.parse(body);
|
|
323
|
+
|
|
324
|
+
const pageIndex = await getPageIndex(getWorkspaceDir());
|
|
325
|
+
const slugs = pageIndex.entries.map((e) => e.slug);
|
|
326
|
+
const scores = computeInjectionScores(getDb(), slugs, Date.now());
|
|
327
|
+
|
|
328
|
+
const entries: MemoryV2EmaScoresEntry[] = pageIndex.entries.map((entry) => ({
|
|
329
|
+
slug: entry.slug,
|
|
330
|
+
score: scores.get(entry.slug) ?? 0,
|
|
331
|
+
modifiedAt: entry.modifiedAt,
|
|
332
|
+
}));
|
|
333
|
+
entries.sort((a, b) => {
|
|
334
|
+
if (a.score !== b.score) return b.score - a.score;
|
|
335
|
+
return a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0;
|
|
336
|
+
});
|
|
337
|
+
return { entries };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ── Simulate router (dry-run playground) ────────────────────────────────
|
|
341
|
+
|
|
342
|
+
const SimulateRouterOverridesSchema = z
|
|
343
|
+
.object({
|
|
344
|
+
tier1_size: z.number().int().min(1).nullable().optional(),
|
|
345
|
+
tier2_size: z.number().int().min(1).nullable().optional(),
|
|
346
|
+
batch_size: z.number().int().min(1).nullable().optional(),
|
|
347
|
+
})
|
|
348
|
+
.strict();
|
|
349
|
+
|
|
350
|
+
const MemoryV2SimulateRouterParams = z
|
|
351
|
+
.object({
|
|
352
|
+
query: z.string().min(1, "query must be non-empty"),
|
|
353
|
+
configOverrides: SimulateRouterOverridesSchema.optional(),
|
|
354
|
+
})
|
|
355
|
+
.strict();
|
|
356
|
+
|
|
357
|
+
export interface MemoryV2SimulateRouterEffectiveConfig {
|
|
358
|
+
tier1_size: number | null;
|
|
359
|
+
tier2_size: number | null;
|
|
360
|
+
batch_size: number | null;
|
|
361
|
+
max_page_ids: number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface MemoryV2SimulateRouterResult {
|
|
365
|
+
/** Slugs the router would select, in model-returned order. */
|
|
366
|
+
selectedSlugs: string[];
|
|
367
|
+
/** Per-slug provenance: `"tier1"`, `"tier2"`, or `"tier3:<bucket>"`. */
|
|
368
|
+
sourceBySlug: Record<string, RouterSource>;
|
|
369
|
+
/** EMA scores for the selected slugs (0 when the slug has no events). */
|
|
370
|
+
scores: Record<string, number>;
|
|
371
|
+
/** `null` on success; otherwise one of the router failure reasons. */
|
|
372
|
+
failureReason: string | null;
|
|
373
|
+
/** The router config that actually ran (live merged with overrides). */
|
|
374
|
+
effectiveConfig: MemoryV2SimulateRouterEffectiveConfig;
|
|
375
|
+
/** The overrides the caller submitted, for display. */
|
|
376
|
+
overrides: {
|
|
377
|
+
tier1_size?: number | null;
|
|
378
|
+
tier2_size?: number | null;
|
|
379
|
+
batch_size?: number | null;
|
|
380
|
+
};
|
|
381
|
+
/** Page index size the router was given (post-tier-carve, all batches). */
|
|
382
|
+
totalCandidatePages: number;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Build the config the router will see by overlaying override values on top
|
|
387
|
+
* of the live workspace config. Only the three new tier knobs are exposed —
|
|
388
|
+
* everything else (provider, prompts, weights) stays exactly as it would on
|
|
389
|
+
* a real turn. `undefined` means "inherit live"; `null` is a valid override
|
|
390
|
+
* value (meaning "disable this tier").
|
|
391
|
+
*/
|
|
392
|
+
function applySimulateOverrides(
|
|
393
|
+
live: AssistantConfig,
|
|
394
|
+
overrides: z.infer<typeof SimulateRouterOverridesSchema> | undefined,
|
|
395
|
+
): AssistantConfig {
|
|
396
|
+
if (!overrides) return live;
|
|
397
|
+
const liveRouter = live.memory.v2.router;
|
|
398
|
+
const mergedRouter = {
|
|
399
|
+
...liveRouter,
|
|
400
|
+
...("tier1_size" in overrides && overrides.tier1_size !== undefined
|
|
401
|
+
? { tier1_size: overrides.tier1_size }
|
|
402
|
+
: {}),
|
|
403
|
+
...("tier2_size" in overrides && overrides.tier2_size !== undefined
|
|
404
|
+
? { tier2_size: overrides.tier2_size }
|
|
405
|
+
: {}),
|
|
406
|
+
...("batch_size" in overrides && overrides.batch_size !== undefined
|
|
407
|
+
? { batch_size: overrides.batch_size }
|
|
408
|
+
: {}),
|
|
409
|
+
};
|
|
410
|
+
return {
|
|
411
|
+
...live,
|
|
412
|
+
memory: {
|
|
413
|
+
...live.memory,
|
|
414
|
+
v2: {
|
|
415
|
+
...live.memory.v2,
|
|
416
|
+
router: mergedRouter,
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function handleSimulateRouter({
|
|
423
|
+
body = {},
|
|
424
|
+
}: RouteHandlerArgs): Promise<MemoryV2SimulateRouterResult> {
|
|
425
|
+
requireMemoryV2Enabled();
|
|
426
|
+
const { query, configOverrides } = MemoryV2SimulateRouterParams.parse(body);
|
|
427
|
+
|
|
428
|
+
const liveConfig = loadConfig();
|
|
429
|
+
const mergedConfig = applySimulateOverrides(liveConfig, configOverrides);
|
|
430
|
+
const effectiveRouter = mergedConfig.memory.v2.router;
|
|
431
|
+
|
|
432
|
+
const workspaceDir = getWorkspaceDir();
|
|
433
|
+
const nowText = await loadNowText(workspaceDir);
|
|
434
|
+
|
|
435
|
+
const routerResult = await runRouter({
|
|
436
|
+
workspaceDir,
|
|
437
|
+
userMessage: query,
|
|
438
|
+
assistantMessage: "",
|
|
439
|
+
nowText,
|
|
440
|
+
priorEverInjected: [],
|
|
441
|
+
config: mergedConfig,
|
|
442
|
+
database: getDb(),
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const pageIndex = await getPageIndex(workspaceDir);
|
|
446
|
+
const scores = computeInjectionScores(
|
|
447
|
+
getDb(),
|
|
448
|
+
routerResult.selectedSlugs,
|
|
449
|
+
Date.now(),
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
const sourceBySlug: Record<string, RouterSource> = {};
|
|
453
|
+
for (const [slug, source] of routerResult.sourceBySlug.entries()) {
|
|
454
|
+
sourceBySlug[slug] = source;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const scoresOut: Record<string, number> = {};
|
|
458
|
+
for (const slug of routerResult.selectedSlugs) {
|
|
459
|
+
scoresOut[slug] = scores.get(slug) ?? 0;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
selectedSlugs: routerResult.selectedSlugs,
|
|
464
|
+
sourceBySlug,
|
|
465
|
+
scores: scoresOut,
|
|
466
|
+
failureReason: routerResult.failureReason,
|
|
467
|
+
effectiveConfig: {
|
|
468
|
+
tier1_size: effectiveRouter.tier1_size,
|
|
469
|
+
tier2_size: effectiveRouter.tier2_size,
|
|
470
|
+
batch_size: effectiveRouter.batch_size,
|
|
471
|
+
max_page_ids: effectiveRouter.max_page_ids,
|
|
472
|
+
},
|
|
473
|
+
overrides: {
|
|
474
|
+
...(configOverrides?.tier1_size !== undefined
|
|
475
|
+
? { tier1_size: configOverrides.tier1_size }
|
|
476
|
+
: {}),
|
|
477
|
+
...(configOverrides?.tier2_size !== undefined
|
|
478
|
+
? { tier2_size: configOverrides.tier2_size }
|
|
479
|
+
: {}),
|
|
480
|
+
...(configOverrides?.batch_size !== undefined
|
|
481
|
+
? { batch_size: configOverrides.batch_size }
|
|
482
|
+
: {}),
|
|
483
|
+
},
|
|
484
|
+
totalCandidatePages: pageIndex.entries.length,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
293
488
|
// ── Route definitions ───────────────────────────────────────────────────
|
|
294
489
|
|
|
295
490
|
export const ROUTES: RouteDefinition[] = [
|
|
@@ -359,4 +554,26 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
359
554
|
tags: ["memory"],
|
|
360
555
|
requestBody: MemoryV2ConceptFrequencyParams,
|
|
361
556
|
},
|
|
557
|
+
{
|
|
558
|
+
operationId: "memory_v2_ema_scores",
|
|
559
|
+
method: "POST",
|
|
560
|
+
endpoint: "memory/v2/ema-scores",
|
|
561
|
+
handler: handleEmaScores,
|
|
562
|
+
summary: "List every concept page with its injection-frequency EMA score",
|
|
563
|
+
description:
|
|
564
|
+
"Computes the time-decayed injection frequency (3-day half-life) for every entry in the current page index by reading memory_v2_injection_events. Returns entries sorted by score descending then slug ASCII, including zero-score pages so callers can decide whether to filter. Read-only; tier 2 of the v4 router uses the same computation to pick its top-M.",
|
|
565
|
+
tags: ["memory"],
|
|
566
|
+
requestBody: MemoryV2EmaScoresParams,
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
operationId: "memory_v2_simulate_router",
|
|
570
|
+
method: "POST",
|
|
571
|
+
endpoint: "memory/v2/simulate-router",
|
|
572
|
+
handler: handleSimulateRouter,
|
|
573
|
+
summary: "Dry-run the v4 router with config overrides (read-only)",
|
|
574
|
+
description:
|
|
575
|
+
"Runs the memory router against the live page index + EMA scores with optional tier_size / batch_size overrides, without recording an injection event or writing an activation log. Returns the slugs that would have been selected, per-slug tier provenance, EMA scores, and the effective router config so operators can validate knob changes before flipping them in workspace config.",
|
|
576
|
+
tags: ["memory"],
|
|
577
|
+
requestBody: MemoryV2SimulateRouterParams,
|
|
578
|
+
},
|
|
362
579
|
];
|
|
@@ -7,6 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
|
|
8
8
|
import { getDb } from "../../memory/db-connection.js";
|
|
9
9
|
import { notificationDeliveries } from "../../memory/schema.js";
|
|
10
|
+
import { bufferIfDeferred } from "../../notifications/deferred-emit.js";
|
|
10
11
|
import { emitNotificationSignal } from "../../notifications/emit-signal.js";
|
|
11
12
|
import { listEvents } from "../../notifications/events-store.js";
|
|
12
13
|
import type { AttentionHints } from "../../notifications/signal.js";
|
|
@@ -78,6 +79,9 @@ const EmitSignalParams = z.object({
|
|
|
78
79
|
conversationAffinityHint: z.record(z.string(), z.string()).optional(),
|
|
79
80
|
dedupeKey: z.string().optional(),
|
|
80
81
|
throwOnError: z.boolean().optional(),
|
|
82
|
+
// Conversation that originated this signal — used by `deferred-emit` to
|
|
83
|
+
// buffer notifications during in-band background-job tool calls.
|
|
84
|
+
originatingConversationId: z.string().optional(),
|
|
81
85
|
});
|
|
82
86
|
|
|
83
87
|
const ListNotificationEventsParams = z.object({
|
|
@@ -89,7 +93,7 @@ const ListNotificationEventsParams = z.object({
|
|
|
89
93
|
|
|
90
94
|
async function handleEmitSignal({ body = {} }: RouteHandlerArgs) {
|
|
91
95
|
const validated = EmitSignalParams.parse(body);
|
|
92
|
-
const
|
|
96
|
+
const params = {
|
|
93
97
|
sourceEventName: validated.sourceEventName,
|
|
94
98
|
sourceChannel: validated.sourceChannel,
|
|
95
99
|
sourceContextId: validated.sourceContextId,
|
|
@@ -99,7 +103,20 @@ async function handleEmitSignal({ body = {} }: RouteHandlerArgs) {
|
|
|
99
103
|
conversationAffinityHint: validated.conversationAffinityHint,
|
|
100
104
|
dedupeKey: validated.dedupeKey,
|
|
101
105
|
throwOnError: validated.throwOnError,
|
|
102
|
-
}
|
|
106
|
+
};
|
|
107
|
+
const buffered = bufferIfDeferred(
|
|
108
|
+
validated.originatingConversationId,
|
|
109
|
+
params,
|
|
110
|
+
);
|
|
111
|
+
if (buffered) {
|
|
112
|
+
return {
|
|
113
|
+
signalId: buffered.signalId,
|
|
114
|
+
dispatched: buffered.dispatched,
|
|
115
|
+
deduplicated: buffered.deduplicated,
|
|
116
|
+
reason: buffered.reason,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const result = await emitNotificationSignal(params);
|
|
103
120
|
return {
|
|
104
121
|
signalId: result.signalId,
|
|
105
122
|
dispatched: result.dispatched,
|
|
@@ -148,7 +148,10 @@ function handleQuestionResponse({ body }: RouteHandlerArgs) {
|
|
|
148
148
|
|
|
149
149
|
// Validation passed — deregister now to clear the prompter timer, then
|
|
150
150
|
// hand the result to the prompter's caller via rpcResolve.
|
|
151
|
-
pendingInteractions.resolve(
|
|
151
|
+
pendingInteractions.resolve(
|
|
152
|
+
requestId,
|
|
153
|
+
response.kind === "close" ? "cancelled" : "answered",
|
|
154
|
+
);
|
|
152
155
|
|
|
153
156
|
log.info(
|
|
154
157
|
{
|