@vellumai/assistant 0.12.3-staging.1 → 0.12.3-staging.2
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 +43 -29
- package/Dockerfile +63 -0
- package/docs/architecture/security.md +10 -5
- package/docs/architecture/turn-actor.md +2 -0
- package/docs/desktop-browser-cli.md +45 -4
- package/docs/live-voice-task-outcomes.md +100 -0
- package/docs/mode-sessions.md +183 -0
- package/docs/voice-input-diagnostics.md +58 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
- package/openapi.yaml +665 -14
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +10 -26
- package/scripts/smoke-desktop-window-theme.ts +2 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
- package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/answered-question-persistence.test.ts +2 -0
- package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
- package/src/__tests__/approval-message-composer.test.ts +1 -118
- package/src/__tests__/approval-routes-http.test.ts +3 -0
- package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
- package/src/__tests__/assistant-event-targeting.test.ts +186 -0
- package/src/__tests__/assistant-stream-state.test.ts +29 -9
- package/src/__tests__/browser-fill-credential.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +4 -1
- package/src/__tests__/build-persisted-content.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +5 -0
- package/src/__tests__/call-recovery.test.ts +7 -2
- package/src/__tests__/call-routes-http.test.ts +12 -0
- package/src/__tests__/call-store.test.ts +7 -2
- package/src/__tests__/channel-retry-sweep.test.ts +9 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
- package/src/__tests__/checker.test.ts +11 -3
- package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
- package/src/__tests__/computer-use-tools.test.ts +1 -233
- package/src/__tests__/config-model-image-provider.test.ts +39 -0
- package/src/__tests__/config-schema.test.ts +16 -0
- package/src/__tests__/config-watcher.test.ts +24 -0
- package/src/__tests__/context-search-agent-runner.test.ts +260 -1
- package/src/__tests__/context-search-fanout.test.ts +1 -1
- package/src/__tests__/context-search-types.test.ts +7 -7
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +546 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
- package/src/__tests__/conversation-evictor.test.ts +62 -37
- package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-retry-route.test.ts +22 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
- package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
- package/src/__tests__/conversation-undo.test.ts +61 -0
- package/src/__tests__/credential-execution-client.test.ts +6 -6
- package/src/__tests__/credential-prompt-route.test.ts +35 -3
- package/src/__tests__/cu-unified-flow.test.ts +82 -1
- package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
- package/src/__tests__/dm-backfill.test.ts +91 -1
- package/src/__tests__/document-create-route.test.ts +88 -0
- package/src/__tests__/document-sync-tags.test.ts +27 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
- package/src/__tests__/events-tail-route.test.ts +25 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
- package/src/__tests__/guardian-action-sweep.test.ts +16 -0
- package/src/__tests__/guardian-dispatch.test.ts +13 -0
- package/src/__tests__/headless-browser-interactions.test.ts +64 -20
- package/src/__tests__/heartbeat-service.test.ts +2 -59
- package/src/__tests__/helpers/mock-conversation.ts +21 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
- package/src/__tests__/host-cu-proxy.test.ts +46 -2
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/image-credentials.test.ts +49 -0
- package/src/__tests__/image-service-dispatcher.test.ts +45 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/interrupt-on-send.test.ts +57 -43
- package/src/__tests__/interrupt-turn-note.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +49 -1
- package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
- package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
- package/src/__tests__/mode-session-history.test.ts +245 -0
- package/src/__tests__/mode-session-recovery.test.ts +222 -0
- package/src/__tests__/mode-session-wire.test.ts +141 -0
- package/src/__tests__/notification-guardian-path.test.ts +7 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
- package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-api-provider.test.ts +5 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
- package/src/__tests__/process-message-display-content.test.ts +56 -0
- package/src/__tests__/reaction-persistence.test.ts +5 -0
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -0
- package/src/__tests__/secure-keys.test.ts +43 -1
- package/src/__tests__/send-endpoint-busy.test.ts +163 -1
- package/src/__tests__/server-history-render.test.ts +91 -1
- package/src/__tests__/settings-routes.test.ts +50 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
- package/src/__tests__/subagent-manager-notify.test.ts +1 -2
- package/src/__tests__/subagent-notify-parent.test.ts +146 -39
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +53 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/twilio-routes.test.ts +1 -0
- package/src/__tests__/web-search-backend-failure.test.ts +1 -1
- package/src/activation/progress-store.test.ts +12 -7
- package/src/agent/loop.ts +99 -41
- package/src/api/events/assistant-turn-start.ts +3 -0
- package/src/api/events/generation-handoff.ts +2 -0
- package/src/api/events/message-complete.ts +2 -0
- package/src/api/events/notification-conversation-created.ts +14 -16
- package/src/api/events/notification-intent.ts +4 -2
- package/src/api/events/tool-result.ts +85 -0
- package/src/api/events/tool-use-start.ts +3 -0
- package/src/api/events/user-message-echo.ts +3 -0
- package/src/api/index.ts +31 -6
- package/src/api/mode-session.test.ts +131 -0
- package/src/api/mode-session.ts +148 -0
- package/src/api/remember-tool.ts +15 -0
- package/src/api/responses/conversation-message.ts +6 -0
- package/src/approvals/AGENTS.md +4 -2
- package/src/browser/__tests__/operations.test.ts +29 -0
- package/src/browser/operation-meta.ts +2 -1
- package/src/browser/operations.ts +29 -0
- package/src/browser/virtual-desktop-target.ts +2 -8
- package/src/bundler/bundle-signer.ts +1 -3
- package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
- package/src/calls/__tests__/call-controller.test.ts +94 -0
- package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
- package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
- package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
- package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
- package/src/calls/barge-in-guard.ts +5 -0
- package/src/calls/call-controller.ts +196 -1
- package/src/calls/call-domain.ts +4 -0
- package/src/calls/call-store.ts +87 -2
- package/src/calls/guardian-action-sweep.ts +22 -8
- package/src/calls/media-stream-audio-transcode.ts +1 -3
- package/src/calls/typesafe-noul.ts +156 -0
- package/src/calls/voice-control-protocol.ts +6 -0
- package/src/calls/voice-escalation-judge.ts +145 -0
- package/src/calls/voice-escalation-target.ts +10 -0
- package/src/calls/voice-leg-coordinator.ts +121 -3
- package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
- package/src/calls/voice-session-bridge.ts +256 -112
- package/src/calls/voice-triage-escalate.ts +7 -0
- package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
- package/src/cli/commands/__tests__/notifications.test.ts +35 -10
- package/src/cli/commands/browser.help.ts +4 -5
- package/src/cli/commands/browser.ts +0 -1
- package/src/cli/commands/clients.help.ts +46 -0
- package/src/cli/commands/clients.ts +116 -0
- package/src/cli/commands/credentials.help.ts +6 -1
- package/src/cli/commands/notifications.help.ts +16 -2
- package/src/cli/commands/notifications.ts +30 -8
- package/src/cli/commands/roadmap.help.ts +18 -21
- package/src/cli/commands/roadmap.ts +4 -21
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
- package/src/cli/lib/bundled-marketplace.json +84 -34
- package/src/cli/lib/plugin-marketplace.ts +8 -0
- package/src/cli/lib/publish-plugin.ts +1 -2
- package/src/config/__tests__/default-provider.test.ts +3 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
- package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
- package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
- package/src/config/call-site-defaults.ts +9 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/profile-text-generation.test.ts +2 -2
- package/src/config/schemas/__tests__/tools.test.ts +31 -0
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -18
- package/src/config/schemas/llm.ts +3 -1
- package/src/config/schemas/mcp.ts +2 -1
- package/src/config/schemas/memory-retrospective.ts +11 -2
- package/src/config/schemas/services.ts +15 -6
- package/src/config/schemas/tools.ts +10 -0
- package/src/config/session-groups-gate.ts +21 -0
- package/src/config/skills.ts +2 -6
- package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
- package/src/conversations/message-consolidation.ts +102 -5
- package/src/credential-execution/ces-connect.test.ts +69 -0
- package/src/credential-execution/ces-connect.ts +115 -0
- package/src/credential-execution/ces-runtime.ts +55 -93
- package/src/credential-execution/executable-discovery.ts +1 -11
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
- package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
- package/src/daemon/browser-mode-session.test.ts +402 -0
- package/src/daemon/browser-mode-session.ts +186 -0
- package/src/daemon/computer-use-mode-session.test.ts +210 -0
- package/src/daemon/computer-use-mode-session.ts +105 -0
- package/src/daemon/config-watcher.ts +10 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
- package/src/daemon/conversation-agent-loop.ts +219 -25
- package/src/daemon/conversation-evictor.ts +5 -24
- package/src/daemon/conversation-history.ts +10 -0
- package/src/daemon/conversation-interrupt-repair.ts +4 -43
- package/src/daemon/conversation-interrupt.ts +7 -11
- package/src/daemon/conversation-messaging.ts +58 -5
- package/src/daemon/conversation-mode-session.test.ts +1667 -0
- package/src/daemon/conversation-mode-session.ts +1008 -0
- package/src/daemon/conversation-process.ts +5 -0
- package/src/daemon/conversation-runtime-assembly.ts +10 -20
- package/src/daemon/conversation-skill-tools.ts +1 -0
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surface-snapshots.ts +135 -0
- package/src/daemon/conversation-surfaces.ts +113 -5
- package/src/daemon/conversation-tool-setup.ts +8 -3
- package/src/daemon/conversation.ts +143 -41
- package/src/daemon/handlers/config-model.ts +7 -5
- package/src/daemon/handlers/shared.ts +55 -16
- package/src/daemon/host-cu-proxy.ts +51 -22
- package/src/daemon/lifecycle.ts +30 -13
- package/src/daemon/message-protocol.ts +0 -1
- package/src/daemon/message-provenance.ts +28 -4
- package/src/daemon/mode-session-startup-recovery.ts +23 -0
- package/src/daemon/mode-session-tracking.ts +54 -0
- package/src/daemon/process-message.ts +13 -4
- package/src/daemon/virtual-desktop-context.ts +21 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-accessibility-script.ts +88 -0
- package/src/desktop/desktop-accessibility.test.ts +160 -0
- package/src/desktop/desktop-accessibility.ts +169 -0
- package/src/desktop/desktop-automation-lease.test.ts +76 -68
- package/src/desktop/desktop-automation-lease.ts +41 -50
- package/src/desktop/desktop-browser-endpoint.ts +4 -0
- package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
- package/src/desktop/desktop-computer-use.test.ts +449 -0
- package/src/desktop/desktop-computer-use.ts +502 -0
- package/src/desktop/desktop-dependencies.ts +47 -293
- package/src/desktop/desktop-display.ts +2 -2
- package/src/desktop/desktop-panel-config.test.ts +13 -3
- package/src/desktop/desktop-panel-config.ts +109 -15
- package/src/desktop/desktop-session-manager.test.ts +69 -21
- package/src/desktop/desktop-session-manager.ts +76 -22
- package/src/desktop/desktop-stream-bridge.test.ts +11 -122
- package/src/desktop/desktop-stream-bridge.ts +6 -8
- package/src/desktop/desktop-window-theme.ts +5 -25
- package/src/desktop/virtual-desktop-feature.ts +52 -2
- package/src/desktop/virtual-desktop-platform.test.ts +5 -9
- package/src/documents/document-store.ts +23 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
- package/src/heartbeat/heartbeat-service.ts +5 -33
- package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
- package/src/ipc/assistant-server.ts +2 -0
- package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
- package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
- package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
- package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
- package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
- package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
- package/src/live-voice/__tests__/session-controls.test.ts +23 -0
- package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
- package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
- package/src/live-voice/camera-mode-session.test.ts +265 -0
- package/src/live-voice/camera-mode-session.ts +277 -0
- package/src/live-voice/continuation-judge.ts +123 -0
- package/src/live-voice/live-activity-reporter.ts +115 -6
- package/src/live-voice/live-voice-manager.ts +36 -1
- package/src/live-voice/live-voice-photo.ts +88 -7
- package/src/live-voice/live-voice-session-manager.ts +15 -0
- package/src/live-voice/live-voice-session.ts +1443 -747
- package/src/live-voice/live-voice-tts.ts +22 -4
- package/src/live-voice/microphone-idle-gate.ts +56 -0
- package/src/live-voice/protocol.ts +171 -4
- package/src/live-voice/session-controls.ts +11 -3
- package/src/live-voice/task-outcomes.ts +131 -0
- package/src/live-voice/voice-input-diagnostics.ts +117 -0
- package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
- package/src/mcp/__tests__/tool-caps.test.ts +28 -1
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/tool-caps.ts +12 -4
- package/src/media/__tests__/image-models.test.ts +58 -0
- package/src/media/image-credentials.ts +5 -0
- package/src/media/image-models.ts +52 -0
- package/src/media/image-service.ts +8 -0
- package/src/media/openrouter-image-service.test.ts +133 -0
- package/src/media/openrouter-image-service.ts +153 -0
- package/src/media/types.ts +1 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
- package/src/messaging/providers/discord/send.ts +4 -17
- package/src/messaging/providers/discord/transport.ts +2 -2
- package/src/messaging/providers/outlook/client.ts +16 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
- package/src/messaging/providers/telegram-bot/send.ts +10 -7
- package/src/messaging/providers/telegram-bot/transport.ts +3 -10
- package/src/messaging/providers/whatsapp/send.test.ts +169 -4
- package/src/monitoring/__tests__/control.test.ts +17 -5
- package/src/monitoring/control.ts +15 -5
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +83 -99
- package/src/notifications/__tests__/broadcaster.test.ts +1 -1
- package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
- package/src/notifications/__tests__/copy-composer.test.ts +1 -1
- package/src/notifications/__tests__/decision-engine.test.ts +79 -2
- package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
- package/src/notifications/__tests__/notification-utils.test.ts +18 -0
- package/src/notifications/adapters/macos.ts +37 -46
- package/src/notifications/broadcaster.ts +5 -6
- package/src/notifications/channel-allowlist.ts +44 -0
- package/src/notifications/copy-composer.ts +0 -23
- package/src/notifications/decision-engine.ts +24 -14
- package/src/notifications/deterministic-checks.ts +1 -1
- package/src/notifications/emit-signal.ts +123 -37
- package/src/notifications/home-feed-side-effect.ts +9 -7
- package/src/notifications/notification-utils.ts +19 -0
- package/src/notifications/signal.ts +4 -19
- package/src/notifications/types.ts +15 -0
- package/src/oauth/connection-resolver.ts +1 -2
- package/src/oauth/oauth-store.ts +1 -3
- package/src/oauth/provider-serializer.ts +7 -0
- package/src/oauth/seed-providers.ts +30 -0
- package/src/onboarding/onboarding-events-store.ts +99 -12
- package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
- package/src/persistence/client-connection-events-store.ts +377 -0
- package/src/persistence/conversation-crud.ts +55 -6
- package/src/persistence/conversation-mode-sessions.test.ts +424 -0
- package/src/persistence/conversation-mode-sessions.ts +460 -0
- package/src/persistence/conversation-queries.ts +6 -19
- package/src/persistence/llm-usage-store.ts +3 -9
- package/src/persistence/message-metadata.ts +25 -0
- package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
- package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
- package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
- package/src/persistence/schema/conversations.ts +67 -0
- package/src/persistence/schema/infrastructure.ts +29 -0
- package/src/persistence/steps.ts +17 -0
- package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
- package/src/plugin-api/effective-context-window.ts +33 -0
- package/src/plugin-api/index.ts +24 -5
- package/src/plugin-api/plugin-name-env.ts +125 -0
- package/src/plugin-api/resolve-credential.ts +79 -20
- package/src/plugin-api/vision-support.test.ts +22 -0
- package/src/plugin-api/vision-support.ts +17 -5
- package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
- package/src/plugins/active-plugin-env.ts +51 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
- package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
- package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
- package/src/plugins/defaults/memory/context-search/format.ts +51 -7
- package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
- package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
- package/src/plugins/defaults/memory/context-search/search.ts +8 -14
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
- package/src/plugins/defaults/memory/context-search/types.ts +1 -11
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
- package/src/plugins/defaults/memory/graph/tools.ts +35 -64
- package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/tools.test.ts +124 -9
- package/src/plugins/defaults/memory/tools.ts +38 -11
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
- package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
- package/src/plugins/defaults/memory/worker-control.ts +15 -5
- package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
- package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
- package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
- package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
- package/src/plugins/defaults/task-progress/package.json +14 -0
- package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
- package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
- package/src/providers/anthropic/client.ts +10 -1
- package/src/providers/anthropic-gateway-shared.ts +6 -0
- package/src/providers/fetch-provider-catalog.ts +11 -0
- package/src/providers/gemini/client.ts +25 -8
- package/src/providers/inference/adapter-factory.ts +1 -1
- package/src/providers/jev/client.test.ts +10 -0
- package/src/providers/jev/client.ts +20 -4
- package/src/providers/model-catalog.ts +4 -3
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
- package/src/providers/openai/chat-completions-provider.ts +100 -29
- package/src/providers/openai/google-thought-signature.ts +0 -6
- package/src/providers/openai/responses-provider.ts +23 -1
- package/src/providers/ratelimit.ts +1 -3
- package/src/providers/search-provider-catalog.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
- package/src/providers/types.ts +5 -0
- package/src/routes/control.ts +7 -5
- package/src/runtime/AGENTS.md +4 -2
- package/src/runtime/actor-trust-resolver.ts +3 -3
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/approval-message-composer.ts +3 -69
- package/src/runtime/assistant-event-hub.ts +73 -75
- package/src/runtime/assistant-event-publish-options.ts +7 -0
- package/src/runtime/assistant-event-targeting.ts +79 -0
- package/src/runtime/assistant-stream-state.ts +13 -100
- package/src/runtime/background-job-runner.ts +5 -5
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +8 -161
- package/src/runtime/guardian-reply-router.ts +28 -32
- package/src/runtime/http-types.ts +6 -0
- package/src/runtime/message-composer-types.ts +3 -61
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
- package/src/runtime/migrations/vbundle-builder.ts +24 -1
- package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
- package/src/runtime/migrations/vbundle-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-validator.ts +1 -0
- package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
- package/src/runtime/pending-interactions.ts +29 -0
- package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
- package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
- package/src/runtime/routes/browser-context.ts +31 -11
- package/src/runtime/routes/browser-routes.ts +43 -4
- package/src/runtime/routes/btw-routes.ts +9 -4
- package/src/runtime/routes/client-routes.ts +108 -0
- package/src/runtime/routes/conversation-management-routes.ts +4 -1
- package/src/runtime/routes/conversation-routes.ts +113 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -3
- package/src/runtime/routes/credential-routes.ts +1 -2
- package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
- package/src/runtime/routes/desktop-setup-routes.ts +4 -6
- package/src/runtime/routes/documents-routes.ts +51 -0
- package/src/runtime/routes/email-routes.ts +3 -7
- package/src/runtime/routes/events-routes.ts +75 -54
- package/src/runtime/routes/identity-routes.ts +1 -5
- package/src/runtime/routes/image-generation-routes.ts +8 -14
- package/src/runtime/routes/inbound-message-handler.ts +35 -38
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +73 -2
- package/src/runtime/routes/notification-routes.ts +10 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +6 -0
- package/src/runtime/routes/roadmap-routes.ts +47 -8
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/settings-routes.ts +10 -4
- package/src/runtime/routes/watcher-routes.ts +1 -3
- package/src/runtime/trust-class.test.ts +7 -6
- package/src/runtime/trust-class.ts +9 -17
- package/src/schedule/__tests__/worker-control.test.ts +36 -5
- package/src/schedule/worker-control.ts +15 -5
- package/src/security/secure-keys.ts +53 -103
- package/src/skills/clawhub.ts +3 -8
- package/src/skills/managed-store.ts +1 -5
- package/src/stt/types.ts +4 -0
- package/src/subagent/notify.ts +54 -34
- package/src/subagent/parent-notification.ts +6 -0
- package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
- package/src/telemetry/phone-call-funnel.ts +144 -0
- package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
- package/src/tools/ask-question/ask-question-tool.ts +6 -4
- package/src/tools/browser/browser-execution.ts +22 -59
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
- package/src/tools/client-os.ts +15 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/computer-use/target.ts +42 -0
- package/src/tools/document/document-tool.ts +13 -18
- package/src/tools/execution-target.ts +14 -16
- package/src/tools/host-terminal/host-shell.ts +4 -1
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
- package/src/tools/network/__tests__/web-search.test.ts +203 -0
- package/src/tools/network/web-fetch.ts +274 -7
- package/src/tools/network/web-search.ts +468 -6
- package/src/tools/permission-checker.ts +3 -3
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/skills/load.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +11 -2
- package/src/tools/skills/skill-script-runner.ts +7 -0
- package/src/tools/skills/skill-tool-factory.ts +15 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/terminal/shell.ts +2 -0
- package/src/tools/tool-approval-handler.ts +5 -1
- package/src/tools/tool-types.ts +3 -3
- package/src/tools/types.ts +8 -2
- package/src/util/__tests__/worker-process-command.test.ts +5 -4
- package/src/util/abort-reasons.ts +18 -26
- package/src/util/canonicalize-identity.test.ts +21 -0
- package/src/util/canonicalize-identity.ts +16 -0
- package/src/util/errors.ts +13 -3
- package/src/util/provider-error-patterns.ts +3 -0
- package/src/util/worker-process.ts +77 -35
- package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
- package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/computer-use-window-schema.test.ts +0 -24
- package/src/api/events/heartbeat-alert.ts +0 -20
- package/src/daemon/message-types/web-activity.ts +0 -65
- package/src/desktop/desktop-dependencies.test.ts +0 -131
- package/src/tools/computer-use/definitions.ts +0 -607
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory v3 — single pool selector.
|
|
3
3
|
*
|
|
4
|
-
* Runs a SINGLE
|
|
4
|
+
* Runs a SINGLE selector call over one unified candidate pool rendered in
|
|
5
5
|
* two segments that share one numbering:
|
|
6
6
|
*
|
|
7
7
|
* 1. STABLE PREFIX — the core+hot lane pages as FULL CARDS (head section +
|
|
@@ -52,11 +52,21 @@
|
|
|
52
52
|
import type {
|
|
53
53
|
ContentBlock,
|
|
54
54
|
Message,
|
|
55
|
+
Provider,
|
|
56
|
+
ProviderResponse,
|
|
55
57
|
ToolUseContent,
|
|
56
58
|
} from "@vellumai/plugin-api";
|
|
57
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
getConfiguredProvider,
|
|
61
|
+
getEffectiveContextWindow,
|
|
62
|
+
safeStringSlice,
|
|
63
|
+
} from "@vellumai/plugin-api";
|
|
58
64
|
import { z } from "zod";
|
|
59
65
|
|
|
66
|
+
import {
|
|
67
|
+
estimatePromptTokensWithTools,
|
|
68
|
+
estimateTextTokens,
|
|
69
|
+
} from "../../../../context/token-estimator.js";
|
|
60
70
|
import { classifyConversationError } from "../../../../daemon/conversation-error.js";
|
|
61
71
|
import type { PendingConversationNotice } from "../../../../daemon/conversation-notices.js";
|
|
62
72
|
import { redactLogString, truncate } from "../host-utils.js";
|
|
@@ -71,6 +81,7 @@ import { retryForResult } from "./llm-retry.js";
|
|
|
71
81
|
import { findTerm } from "./section-needle.js";
|
|
72
82
|
import { sectionBody } from "./sections.js";
|
|
73
83
|
import {
|
|
84
|
+
type FinderLane,
|
|
74
85
|
type MemoryRoutingTurn,
|
|
75
86
|
type Section,
|
|
76
87
|
sectionKey,
|
|
@@ -92,12 +103,16 @@ const log = getLogger("memory-v3-pool-select");
|
|
|
92
103
|
*/
|
|
93
104
|
export class MemoryV3RetrievalUnavailableError extends Error {
|
|
94
105
|
readonly conversationNotice?: PendingConversationNotice;
|
|
106
|
+
readonly pool?: SelectorPool;
|
|
107
|
+
readonly turn?: MemoryRoutingTurn;
|
|
95
108
|
|
|
96
109
|
constructor(
|
|
97
110
|
message: string,
|
|
98
111
|
options?: {
|
|
99
112
|
cause?: unknown;
|
|
100
113
|
conversationNotice?: PendingConversationNotice;
|
|
114
|
+
pool?: SelectorPool;
|
|
115
|
+
turn?: MemoryRoutingTurn;
|
|
101
116
|
},
|
|
102
117
|
) {
|
|
103
118
|
super(
|
|
@@ -106,6 +121,8 @@ export class MemoryV3RetrievalUnavailableError extends Error {
|
|
|
106
121
|
);
|
|
107
122
|
this.name = "MemoryV3RetrievalUnavailableError";
|
|
108
123
|
this.conversationNotice = options?.conversationNotice;
|
|
124
|
+
this.pool = options?.pool;
|
|
125
|
+
this.turn = options?.turn;
|
|
109
126
|
}
|
|
110
127
|
}
|
|
111
128
|
|
|
@@ -129,7 +146,7 @@ function providerBillingNoticeFromError(
|
|
|
129
146
|
/** A dynamic-tail (finder) candidate: the slug plus the descriptor that
|
|
130
147
|
* justifies it (a matched section's text for a needle/dense hit, or a
|
|
131
148
|
* curated link description for an edge page), rendered as a one-line
|
|
132
|
-
* snippet and prefixed with the surfacing lane
|
|
149
|
+
* snippet and prefixed with the surfacing lane. A
|
|
133
150
|
* candidate that also carries its matched `section` and the query `terms`
|
|
134
151
|
* that scored it (best first) renders a keyword-in-context snippet instead:
|
|
135
152
|
* a window of the section body around the first of those terms that occurs
|
|
@@ -140,7 +157,7 @@ function providerBillingNoticeFromError(
|
|
|
140
157
|
export interface PoolCandidate {
|
|
141
158
|
slug: Slug;
|
|
142
159
|
descriptor: string;
|
|
143
|
-
lane
|
|
160
|
+
lane: FinderLane;
|
|
144
161
|
section?: Section;
|
|
145
162
|
terms?: string[];
|
|
146
163
|
}
|
|
@@ -149,9 +166,12 @@ export interface PoolCandidate {
|
|
|
149
166
|
* (`renderCard` output — head section + TOC). Cards must be byte-stable
|
|
150
167
|
* across turns for the prefix to ride the provider KV cache, which is why
|
|
151
168
|
* callers pre-render them at lane init rather than per turn. */
|
|
169
|
+
export type StableCandidateLane = "core" | "hot" | "fresh" | "always";
|
|
170
|
+
|
|
152
171
|
export interface StableCandidate {
|
|
153
172
|
slug: Slug;
|
|
154
173
|
card: string;
|
|
174
|
+
lane: StableCandidateLane;
|
|
155
175
|
}
|
|
156
176
|
|
|
157
177
|
/**
|
|
@@ -170,6 +190,7 @@ export interface SelectorPool {
|
|
|
170
190
|
/** Tool name forced via `tool_choice`. Shared constant so tests can match it. */
|
|
171
191
|
const SELECT_PAGES_TOOL_NAME = "select_pages";
|
|
172
192
|
const MEMORY_V3_SELECT_CALL_SITE = "memoryV3SelectL2" as const;
|
|
193
|
+
const SELECTOR_CONTEXT_BUDGET_RATIO = 0.8;
|
|
173
194
|
|
|
174
195
|
/** Finder-line snippets are truncated to keep the dynamic tail compact. */
|
|
175
196
|
const SNIPPET_MAX_CHARS = 300;
|
|
@@ -179,7 +200,8 @@ type PoolSelectorAttemptFailureReason =
|
|
|
179
200
|
| "provider_error"
|
|
180
201
|
| "missing_tool_use"
|
|
181
202
|
| "unexpected_tool_name"
|
|
182
|
-
| "schema_mismatch"
|
|
203
|
+
| "schema_mismatch"
|
|
204
|
+
| "unusable_answers";
|
|
183
205
|
|
|
184
206
|
interface PoolSelectorAttemptFailure {
|
|
185
207
|
attempt: number;
|
|
@@ -396,12 +418,8 @@ function renderCardSegment(stable: StableCandidate[]): string {
|
|
|
396
418
|
}
|
|
397
419
|
|
|
398
420
|
/** A finder line's lane tag: `(lane) `, or `(rare: word) ` for a rare-term
|
|
399
|
-
* line, keyed on the one word it carries as its term
|
|
400
|
-
* without a lane. */
|
|
421
|
+
* line, keyed on the one word it carries as its term. */
|
|
401
422
|
function laneTag(candidate: PoolCandidate): string {
|
|
402
|
-
if (candidate.lane === undefined) {
|
|
403
|
-
return "";
|
|
404
|
-
}
|
|
405
423
|
const word = candidate.lane === "rare" ? candidate.terms?.[0] : undefined;
|
|
406
424
|
return word === undefined
|
|
407
425
|
? `(${candidate.lane}) `
|
|
@@ -410,8 +428,8 @@ function laneTag(candidate: PoolCandidate): string {
|
|
|
410
428
|
|
|
411
429
|
/**
|
|
412
430
|
* One finder line as the selector sees it, minus its pool number: the lane
|
|
413
|
-
* tag (
|
|
414
|
-
*
|
|
431
|
+
* tag (naming the keyed word for a rare-term line, `(rare: turnip)`), the
|
|
432
|
+
* slug, and the snippet after a dash
|
|
415
433
|
* (no dash for a candidate with an empty descriptor). Shared with the pool
|
|
416
434
|
* input capture (`pool-log-store.ts`), so the persisted text is exactly the
|
|
417
435
|
* rendered line.
|
|
@@ -493,9 +511,563 @@ export function selectAllPoolCandidates(pool: SelectorPool): SelectedPage[] {
|
|
|
493
511
|
);
|
|
494
512
|
}
|
|
495
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Inclusive noul threshold for TypeSafe pool selection. 0.5 is calibrated
|
|
516
|
+
* equal yes/no. This sits slightly below that so a plausible candidate is
|
|
517
|
+
* kept, matching the selector's recall-heavy rule.
|
|
518
|
+
*/
|
|
519
|
+
export const TYPE_SAFE_POOL_KEEP_NOUL = 0.4;
|
|
520
|
+
|
|
521
|
+
/** Catalog id of the TypeSafe System One provider. */
|
|
522
|
+
const TYPE_SAFE_PROVIDER_ID = "typesafe";
|
|
523
|
+
|
|
524
|
+
type TypesafeNoulQuestion = {
|
|
525
|
+
type: "noul";
|
|
526
|
+
instructions: string;
|
|
527
|
+
criteria: { yes: string; no: string };
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
531
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function noulFromAnswer(answer: unknown): number | undefined {
|
|
535
|
+
if (typeof answer === "number" && Number.isFinite(answer)) {
|
|
536
|
+
return answer;
|
|
537
|
+
}
|
|
538
|
+
if (
|
|
539
|
+
isRecord(answer) &&
|
|
540
|
+
typeof answer.noul === "number" &&
|
|
541
|
+
Number.isFinite(answer.noul)
|
|
542
|
+
) {
|
|
543
|
+
return answer.noul;
|
|
544
|
+
}
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function typesafeCandidateEntries(
|
|
549
|
+
pool: SelectorPool,
|
|
550
|
+
): Record<string, { slug: Slug; text: string }> {
|
|
551
|
+
const entries: Record<string, { slug: Slug; text: string }> = {};
|
|
552
|
+
pool.stable.forEach((candidate, index) => {
|
|
553
|
+
entries[String(index + 1)] = {
|
|
554
|
+
slug: candidate.slug,
|
|
555
|
+
text: candidate.card,
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
pool.finder.forEach((candidate, index) => {
|
|
559
|
+
entries[String(pool.stable.length + index + 1)] = {
|
|
560
|
+
slug: candidate.slug,
|
|
561
|
+
text: renderFinderLine(candidate),
|
|
562
|
+
};
|
|
563
|
+
});
|
|
564
|
+
return entries;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function typesafeKeepQuestion(id: string): TypesafeNoulQuestion {
|
|
568
|
+
return {
|
|
569
|
+
type: "noul",
|
|
570
|
+
instructions:
|
|
571
|
+
`Would the upcoming assistant reply draw on \`candidates.${id}\`? ` +
|
|
572
|
+
"Lean inclusive. Facts, current task and event state, register, " +
|
|
573
|
+
"framing, calibration, and relationship texture all count. True when " +
|
|
574
|
+
"the candidate could plausibly inform the reply.",
|
|
575
|
+
criteria: {
|
|
576
|
+
yes: "The reply would draw on this candidate.",
|
|
577
|
+
no: "The reply would not draw on this candidate.",
|
|
578
|
+
},
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function buildTypesafeSelectorPayload(
|
|
583
|
+
pool: SelectorPool,
|
|
584
|
+
turn: MemoryRoutingTurn,
|
|
585
|
+
systemPrompt: string,
|
|
586
|
+
): {
|
|
587
|
+
state: Record<string, unknown>;
|
|
588
|
+
questions: Record<string, TypesafeNoulQuestion>;
|
|
589
|
+
} {
|
|
590
|
+
const candidates = typesafeCandidateEntries(pool);
|
|
591
|
+
const questions: Record<string, TypesafeNoulQuestion> = {};
|
|
592
|
+
for (const id of Object.keys(candidates)) {
|
|
593
|
+
questions[id] = typesafeKeepQuestion(id);
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
state: {
|
|
597
|
+
selector_instructions: systemPrompt,
|
|
598
|
+
candidates,
|
|
599
|
+
...(turn.situationalContext
|
|
600
|
+
? { situation: turn.situationalContext }
|
|
601
|
+
: {}),
|
|
602
|
+
recent_context: turn.recentContext,
|
|
603
|
+
current_message: turn.currentMessage,
|
|
604
|
+
},
|
|
605
|
+
questions,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function buildGenericSelectorMessage(
|
|
610
|
+
pool: SelectorPool,
|
|
611
|
+
turn: MemoryRoutingTurn,
|
|
612
|
+
): Message {
|
|
613
|
+
const content: ContentBlock[] = [];
|
|
614
|
+
if (pool.stable.length > 0) {
|
|
615
|
+
content.push(cachedTextBlock(renderCardSegment(pool.stable)));
|
|
616
|
+
}
|
|
617
|
+
const tailParts: string[] = [];
|
|
618
|
+
if (pool.finder.length > 0) {
|
|
619
|
+
tailParts.push(renderFinderSegment(pool.finder, pool.stable.length));
|
|
620
|
+
}
|
|
621
|
+
if (turn.situationalContext) {
|
|
622
|
+
tailParts.push(`<situation>${turn.situationalContext}</situation>`);
|
|
623
|
+
}
|
|
624
|
+
tailParts.push(`<recent_context>${turn.recentContext}</recent_context>`);
|
|
625
|
+
tailParts.push(`<current_message>${turn.currentMessage}</current_message>`);
|
|
626
|
+
content.push({ type: "text", text: tailParts.join("\n") });
|
|
627
|
+
return { role: "user", content };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const STABLE_CANDIDATE_PRIORITY: Record<StableCandidateLane, number> = {
|
|
631
|
+
core: 0,
|
|
632
|
+
hot: 5,
|
|
633
|
+
fresh: 6,
|
|
634
|
+
always: 7,
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const FINDER_CANDIDATE_PRIORITY: Record<FinderLane, number> = {
|
|
638
|
+
needle: 1,
|
|
639
|
+
dense: 2,
|
|
640
|
+
reply: 3,
|
|
641
|
+
span: 4,
|
|
642
|
+
entity: 8,
|
|
643
|
+
rare: 9,
|
|
644
|
+
edge: 10,
|
|
645
|
+
learned: 11,
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
function highestPriorityCandidates(
|
|
649
|
+
pool: SelectorPool,
|
|
650
|
+
retainedCount: number,
|
|
651
|
+
): SelectorPool {
|
|
652
|
+
const ranked = [
|
|
653
|
+
...pool.stable.map((candidate, index) => ({
|
|
654
|
+
segment: "stable" as const,
|
|
655
|
+
index,
|
|
656
|
+
poolIndex: index,
|
|
657
|
+
priority: STABLE_CANDIDATE_PRIORITY[candidate.lane],
|
|
658
|
+
})),
|
|
659
|
+
...pool.finder.map((candidate, index) => ({
|
|
660
|
+
segment: "finder" as const,
|
|
661
|
+
index,
|
|
662
|
+
poolIndex: pool.stable.length + index,
|
|
663
|
+
priority: FINDER_CANDIDATE_PRIORITY[candidate.lane],
|
|
664
|
+
})),
|
|
665
|
+
]
|
|
666
|
+
.sort(
|
|
667
|
+
(left, right) =>
|
|
668
|
+
left.priority - right.priority || left.poolIndex - right.poolIndex,
|
|
669
|
+
)
|
|
670
|
+
.slice(0, retainedCount);
|
|
671
|
+
const retainedStable = new Set(
|
|
672
|
+
ranked
|
|
673
|
+
.filter((candidate) => candidate.segment === "stable")
|
|
674
|
+
.map((candidate) => candidate.index),
|
|
675
|
+
);
|
|
676
|
+
const retainedFinder = new Set(
|
|
677
|
+
ranked
|
|
678
|
+
.filter((candidate) => candidate.segment === "finder")
|
|
679
|
+
.map((candidate) => candidate.index),
|
|
680
|
+
);
|
|
681
|
+
return {
|
|
682
|
+
stable: pool.stable.filter((_, index) => retainedStable.has(index)),
|
|
683
|
+
finder: pool.finder.filter((_, index) => retainedFinder.has(index)),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function withoutTrailingCurrentMessage(
|
|
688
|
+
turn: MemoryRoutingTurn,
|
|
689
|
+
): MemoryRoutingTurn {
|
|
690
|
+
if (turn.recentContext === turn.currentMessage) {
|
|
691
|
+
return { ...turn, recentContext: "" };
|
|
692
|
+
}
|
|
693
|
+
const duplicateSuffix = `\n${turn.currentMessage}`;
|
|
694
|
+
if (
|
|
695
|
+
turn.currentMessage.length === 0 ||
|
|
696
|
+
!turn.recentContext.endsWith(duplicateSuffix)
|
|
697
|
+
) {
|
|
698
|
+
return turn;
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
...turn,
|
|
702
|
+
recentContext: turn.recentContext.slice(0, -duplicateSuffix.length),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function latestTurnContextSuffix(
|
|
707
|
+
turn: MemoryRoutingTurn,
|
|
708
|
+
retainedChars: number,
|
|
709
|
+
): MemoryRoutingTurn {
|
|
710
|
+
let remaining = retainedChars;
|
|
711
|
+
const takeSuffix = (value: string): string => {
|
|
712
|
+
const count = Math.min(remaining, value.length);
|
|
713
|
+
remaining -= count;
|
|
714
|
+
return safeStringSlice(value, value.length - count);
|
|
715
|
+
};
|
|
716
|
+
const currentMessage = takeSuffix(turn.currentMessage);
|
|
717
|
+
const recentContext = takeSuffix(turn.recentContext);
|
|
718
|
+
const situationalContext = turn.situationalContext
|
|
719
|
+
? takeSuffix(turn.situationalContext) || undefined
|
|
720
|
+
: undefined;
|
|
721
|
+
return {
|
|
722
|
+
...turn,
|
|
723
|
+
currentMessage,
|
|
724
|
+
recentContext,
|
|
725
|
+
situationalContext,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function turnContextChars(turn: MemoryRoutingTurn): number {
|
|
730
|
+
return (
|
|
731
|
+
turn.currentMessage.length +
|
|
732
|
+
turn.recentContext.length +
|
|
733
|
+
(turn.situationalContext?.length ?? 0)
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function estimateSelectorInputTokens({
|
|
738
|
+
pool,
|
|
739
|
+
turn,
|
|
740
|
+
systemPrompt,
|
|
741
|
+
provider,
|
|
742
|
+
model,
|
|
743
|
+
}: {
|
|
744
|
+
pool: SelectorPool;
|
|
745
|
+
turn: MemoryRoutingTurn;
|
|
746
|
+
systemPrompt: string;
|
|
747
|
+
provider: Provider;
|
|
748
|
+
model: string;
|
|
749
|
+
}): number {
|
|
750
|
+
if (provider.name === TYPE_SAFE_PROVIDER_ID) {
|
|
751
|
+
const payload = buildTypesafeSelectorPayload(pool, turn, systemPrompt);
|
|
752
|
+
return estimateTextTokens(
|
|
753
|
+
JSON.stringify({
|
|
754
|
+
state: payload.state,
|
|
755
|
+
model,
|
|
756
|
+
questions: payload.questions,
|
|
757
|
+
}),
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return estimatePromptTokensWithTools(
|
|
761
|
+
[buildGenericSelectorMessage(pool, turn)],
|
|
762
|
+
systemPrompt,
|
|
763
|
+
[SELECT_PAGES_TOOL],
|
|
764
|
+
provider.tokenEstimationProvider ?? provider.name,
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
interface SelectorPoolBudget {
|
|
769
|
+
pool: SelectorPool;
|
|
770
|
+
turn: MemoryRoutingTurn;
|
|
771
|
+
budgetTokens: number;
|
|
772
|
+
estimatedInputTokens: number;
|
|
773
|
+
originalEstimatedInputTokens: number;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function budgetSelectorPool({
|
|
777
|
+
pool,
|
|
778
|
+
turn,
|
|
779
|
+
systemPrompt,
|
|
780
|
+
provider,
|
|
781
|
+
model,
|
|
782
|
+
maxInputTokens,
|
|
783
|
+
}: {
|
|
784
|
+
pool: SelectorPool;
|
|
785
|
+
turn: MemoryRoutingTurn;
|
|
786
|
+
systemPrompt: string;
|
|
787
|
+
provider: Provider;
|
|
788
|
+
model: string;
|
|
789
|
+
maxInputTokens: number;
|
|
790
|
+
}): SelectorPoolBudget {
|
|
791
|
+
const budgetTokens = Math.max(
|
|
792
|
+
1,
|
|
793
|
+
Math.floor(maxInputTokens * SELECTOR_CONTEXT_BUDGET_RATIO),
|
|
794
|
+
);
|
|
795
|
+
const estimate = (
|
|
796
|
+
candidatePool: SelectorPool,
|
|
797
|
+
candidateTurn: MemoryRoutingTurn,
|
|
798
|
+
): number =>
|
|
799
|
+
estimateSelectorInputTokens({
|
|
800
|
+
pool: candidatePool,
|
|
801
|
+
turn: candidateTurn,
|
|
802
|
+
systemPrompt,
|
|
803
|
+
provider,
|
|
804
|
+
model,
|
|
805
|
+
});
|
|
806
|
+
const originalEstimatedInputTokens = estimate(pool, turn);
|
|
807
|
+
if (originalEstimatedInputTokens <= budgetTokens) {
|
|
808
|
+
return {
|
|
809
|
+
pool,
|
|
810
|
+
turn,
|
|
811
|
+
budgetTokens,
|
|
812
|
+
estimatedInputTokens: originalEstimatedInputTokens,
|
|
813
|
+
originalEstimatedInputTokens,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const deduplicatedTurn = withoutTrailingCurrentMessage(turn);
|
|
818
|
+
const deduplicatedEstimatedInputTokens = estimate(pool, deduplicatedTurn);
|
|
819
|
+
if (deduplicatedEstimatedInputTokens <= budgetTokens) {
|
|
820
|
+
return {
|
|
821
|
+
pool,
|
|
822
|
+
turn: deduplicatedTurn,
|
|
823
|
+
budgetTokens,
|
|
824
|
+
estimatedInputTokens: deduplicatedEstimatedInputTokens,
|
|
825
|
+
originalEstimatedInputTokens,
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const minimumTurn = latestTurnContextSuffix(
|
|
830
|
+
deduplicatedTurn,
|
|
831
|
+
deduplicatedTurn.currentMessage.length,
|
|
832
|
+
);
|
|
833
|
+
const fittingPool: SelectorPool = {
|
|
834
|
+
stable: pool.stable.filter(
|
|
835
|
+
(candidate) =>
|
|
836
|
+
estimate({ stable: [candidate], finder: [] }, minimumTurn) <=
|
|
837
|
+
budgetTokens,
|
|
838
|
+
),
|
|
839
|
+
finder: pool.finder.filter(
|
|
840
|
+
(candidate) =>
|
|
841
|
+
estimate({ stable: [], finder: [candidate] }, minimumTurn) <=
|
|
842
|
+
budgetTokens,
|
|
843
|
+
),
|
|
844
|
+
};
|
|
845
|
+
const candidateCount = fittingPool.stable.length + fittingPool.finder.length;
|
|
846
|
+
if (candidateCount === 0) {
|
|
847
|
+
const emptyPool: SelectorPool = { stable: [], finder: [] };
|
|
848
|
+
return {
|
|
849
|
+
pool: emptyPool,
|
|
850
|
+
turn: minimumTurn,
|
|
851
|
+
budgetTokens,
|
|
852
|
+
estimatedInputTokens: estimate(emptyPool, minimumTurn),
|
|
853
|
+
originalEstimatedInputTokens,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const preferredCandidatePool = highestPriorityCandidates(fittingPool, 1);
|
|
858
|
+
let budgetedTurn = deduplicatedTurn;
|
|
859
|
+
if (estimate(preferredCandidatePool, budgetedTurn) > budgetTokens) {
|
|
860
|
+
let low = deduplicatedTurn.currentMessage.length;
|
|
861
|
+
let high = turnContextChars(deduplicatedTurn);
|
|
862
|
+
while (low < high) {
|
|
863
|
+
const retainedChars = Math.ceil((low + high) / 2);
|
|
864
|
+
const candidateTurn = latestTurnContextSuffix(
|
|
865
|
+
deduplicatedTurn,
|
|
866
|
+
retainedChars,
|
|
867
|
+
);
|
|
868
|
+
if (estimate(preferredCandidatePool, candidateTurn) <= budgetTokens) {
|
|
869
|
+
low = retainedChars;
|
|
870
|
+
} else {
|
|
871
|
+
high = retainedChars - 1;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
budgetedTurn = latestTurnContextSuffix(deduplicatedTurn, low);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
let low = 1;
|
|
878
|
+
let high = candidateCount;
|
|
879
|
+
while (low < high) {
|
|
880
|
+
const retainedCount = Math.ceil((low + high) / 2);
|
|
881
|
+
const candidatePool = highestPriorityCandidates(fittingPool, retainedCount);
|
|
882
|
+
if (estimate(candidatePool, budgetedTurn) <= budgetTokens) {
|
|
883
|
+
low = retainedCount;
|
|
884
|
+
} else {
|
|
885
|
+
high = retainedCount - 1;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const budgetedPool = highestPriorityCandidates(fittingPool, low);
|
|
890
|
+
return {
|
|
891
|
+
pool: budgetedPool,
|
|
892
|
+
turn: budgetedTurn,
|
|
893
|
+
budgetTokens,
|
|
894
|
+
estimatedInputTokens: estimate(budgetedPool, budgetedTurn),
|
|
895
|
+
originalEstimatedInputTokens,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function answersFromSelectorResponse(
|
|
900
|
+
response: ProviderResponse,
|
|
901
|
+
): Record<string, unknown> | null {
|
|
902
|
+
const raw = response.rawResponse;
|
|
903
|
+
if (isRecord(raw) && isRecord(raw.answers)) {
|
|
904
|
+
return raw.answers;
|
|
905
|
+
}
|
|
906
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
907
|
+
if (!textBlock || textBlock.type !== "text") {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
try {
|
|
911
|
+
const parsed: unknown = JSON.parse(textBlock.text);
|
|
912
|
+
return isRecord(parsed) ? parsed : null;
|
|
913
|
+
} catch {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
async function selectPoolWithTypesafe(
|
|
919
|
+
pool: SelectorPool,
|
|
920
|
+
turn: MemoryRoutingTurn,
|
|
921
|
+
ordered: PoolLine[],
|
|
922
|
+
systemPrompt: string,
|
|
923
|
+
provider: Provider,
|
|
924
|
+
): Promise<PoolSelection> {
|
|
925
|
+
const payload = buildTypesafeSelectorPayload(pool, turn, systemPrompt);
|
|
926
|
+
const userMsg: Message = {
|
|
927
|
+
role: "user",
|
|
928
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
const failures: PoolSelectorAttemptFailure[] = [];
|
|
932
|
+
let attempt = 0;
|
|
933
|
+
const recordFailure = (
|
|
934
|
+
failure: Omit<
|
|
935
|
+
PoolSelectorAttemptFailure,
|
|
936
|
+
| "callSite"
|
|
937
|
+
| "providerName"
|
|
938
|
+
| "candidateCount"
|
|
939
|
+
| "stableCount"
|
|
940
|
+
| "finderCount"
|
|
941
|
+
>,
|
|
942
|
+
): void => {
|
|
943
|
+
const diagnostic: PoolSelectorAttemptFailure = {
|
|
944
|
+
...failure,
|
|
945
|
+
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
946
|
+
providerName: provider.name,
|
|
947
|
+
candidateCount: ordered.length,
|
|
948
|
+
stableCount: pool.stable.length,
|
|
949
|
+
finderCount: pool.finder.length,
|
|
950
|
+
};
|
|
951
|
+
failures.push(diagnostic);
|
|
952
|
+
log.warn(diagnostic, "pool selector attempt failed");
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
let lastError: unknown = null;
|
|
956
|
+
const parsed = await retryForResult(async () => {
|
|
957
|
+
attempt += 1;
|
|
958
|
+
let response: Awaited<ReturnType<typeof provider.sendMessage>>;
|
|
959
|
+
try {
|
|
960
|
+
response = await provider.sendMessage([userMsg], {
|
|
961
|
+
config: {
|
|
962
|
+
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
963
|
+
conversationId: turn.conversationId,
|
|
964
|
+
selectionSeed: turn.conversationId,
|
|
965
|
+
disableTurnStartCache: true,
|
|
966
|
+
},
|
|
967
|
+
});
|
|
968
|
+
lastError = null;
|
|
969
|
+
} catch (error) {
|
|
970
|
+
lastError = error;
|
|
971
|
+
recordFailure({
|
|
972
|
+
attempt,
|
|
973
|
+
reason: "provider_error",
|
|
974
|
+
error: summarizeError(error),
|
|
975
|
+
});
|
|
976
|
+
throw error;
|
|
977
|
+
}
|
|
978
|
+
const answers = answersFromSelectorResponse(response);
|
|
979
|
+
if (!answers) {
|
|
980
|
+
recordFailure({
|
|
981
|
+
attempt,
|
|
982
|
+
reason: "unusable_answers",
|
|
983
|
+
response: summarizeResponse(response),
|
|
984
|
+
});
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
const picked: number[] = [];
|
|
988
|
+
let parsedCount = 0;
|
|
989
|
+
for (let index = 0; index < ordered.length; index++) {
|
|
990
|
+
const noul = noulFromAnswer(answers[String(index + 1)]);
|
|
991
|
+
if (noul === undefined) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
parsedCount += 1;
|
|
995
|
+
if (noul >= TYPE_SAFE_POOL_KEEP_NOUL) {
|
|
996
|
+
picked.push(index);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (parsedCount === 0) {
|
|
1000
|
+
recordFailure({
|
|
1001
|
+
attempt,
|
|
1002
|
+
reason: "unusable_answers",
|
|
1003
|
+
response: summarizeResponse(response),
|
|
1004
|
+
});
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
pages: mergeSelectedLines(ordered, picked),
|
|
1009
|
+
keptAll: false,
|
|
1010
|
+
pool,
|
|
1011
|
+
turn,
|
|
1012
|
+
};
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
if (parsed === null) {
|
|
1016
|
+
if (lastError !== null) {
|
|
1017
|
+
const detail =
|
|
1018
|
+
lastError instanceof Error ? lastError.message : String(lastError);
|
|
1019
|
+
const redactedDetail = truncate(
|
|
1020
|
+
redactLogString(detail),
|
|
1021
|
+
ERROR_MESSAGE_MAX_CHARS,
|
|
1022
|
+
);
|
|
1023
|
+
log.warn(
|
|
1024
|
+
{
|
|
1025
|
+
candidateCount: ordered.length,
|
|
1026
|
+
stableCount: pool.stable.length,
|
|
1027
|
+
finderCount: pool.finder.length,
|
|
1028
|
+
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
1029
|
+
providerName: provider.name,
|
|
1030
|
+
failures,
|
|
1031
|
+
},
|
|
1032
|
+
"pool selector provider call failed after retries",
|
|
1033
|
+
);
|
|
1034
|
+
throw new MemoryV3RetrievalUnavailableError(
|
|
1035
|
+
`memory-v3 pool selector provider call failed after retries: ${redactedDetail}`,
|
|
1036
|
+
{
|
|
1037
|
+
cause: lastError,
|
|
1038
|
+
conversationNotice: providerBillingNoticeFromError(lastError),
|
|
1039
|
+
pool,
|
|
1040
|
+
turn,
|
|
1041
|
+
},
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
log.warn(
|
|
1045
|
+
{
|
|
1046
|
+
candidateCount: ordered.length,
|
|
1047
|
+
stableCount: pool.stable.length,
|
|
1048
|
+
finderCount: pool.finder.length,
|
|
1049
|
+
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
1050
|
+
providerName: provider.name,
|
|
1051
|
+
failures,
|
|
1052
|
+
},
|
|
1053
|
+
"pool selector returned no usable TypeSafe answers after retries",
|
|
1054
|
+
);
|
|
1055
|
+
throw new MemoryV3RetrievalUnavailableError(
|
|
1056
|
+
"memory-v3 pool selector returned no usable selection after retries",
|
|
1057
|
+
{ pool, turn },
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
return parsed;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
496
1064
|
/** A selection plus whether it came from the recall-safe keep-all fallback. */
|
|
497
1065
|
export interface PoolSelection {
|
|
498
1066
|
pages: SelectedPage[];
|
|
1067
|
+
/** The exact candidate pool rendered for the selector request. */
|
|
1068
|
+
pool: SelectorPool;
|
|
1069
|
+
/** The exact context strings rendered for the selector request. */
|
|
1070
|
+
turn: MemoryRoutingTurn;
|
|
499
1071
|
/** True only when the model OMITTED `ids` and every candidate was kept as the
|
|
500
1072
|
* recall-safe fallback — NOT when it explicitly selected a large set. The two
|
|
501
1073
|
* produce the same pages but mean different things (the model gave up judging
|
|
@@ -504,14 +1076,16 @@ export interface PoolSelection {
|
|
|
504
1076
|
}
|
|
505
1077
|
|
|
506
1078
|
/**
|
|
507
|
-
* Run the single
|
|
1079
|
+
* Run the single selector over the unified candidate pool. Returns
|
|
508
1080
|
* the pages to inject, merged per slug (a page selected as a card and on
|
|
509
1081
|
* finder lines yields one entry carrying every selected section), plus a
|
|
510
1082
|
* `keptAll` flag marking the recall-safe fallback.
|
|
511
1083
|
*
|
|
512
|
-
*
|
|
513
|
-
* relevant" signal, `keptAll: true`); an explicit `[]`
|
|
514
|
-
*
|
|
1084
|
+
* On a chat model, an omitted `ids` keeps ALL candidates (the recall-safe
|
|
1085
|
+
* "all of these are relevant" signal, `keptAll: true`); an explicit `[]`
|
|
1086
|
+
* keeps none. TypeSafe answers one noul per candidate and never omits ids,
|
|
1087
|
+
* so `keptAll` is always false on that path. An infrastructure failure
|
|
1088
|
+
* (after a short re-prompt retry) throws
|
|
515
1089
|
* {@link MemoryV3RetrievalUnavailableError}, and the orchestrator keeps the
|
|
516
1090
|
* stable prefix unjudged in its place.
|
|
517
1091
|
*
|
|
@@ -524,17 +1098,19 @@ export async function selectPool(
|
|
|
524
1098
|
turn: MemoryRoutingTurn,
|
|
525
1099
|
systemPrompt: string = SYSTEM_PROMPT,
|
|
526
1100
|
): Promise<PoolSelection> {
|
|
527
|
-
const
|
|
528
|
-
if (
|
|
529
|
-
return { pages: [], keptAll: false };
|
|
1101
|
+
const originalCandidateCount = pool.stable.length + pool.finder.length;
|
|
1102
|
+
if (originalCandidateCount === 0) {
|
|
1103
|
+
return { pages: [], keptAll: false, pool, turn };
|
|
530
1104
|
}
|
|
531
1105
|
|
|
532
|
-
const provider = await getConfiguredProvider(MEMORY_V3_SELECT_CALL_SITE
|
|
1106
|
+
const provider = await getConfiguredProvider(MEMORY_V3_SELECT_CALL_SITE, {
|
|
1107
|
+
selectionSeed: turn.conversationId,
|
|
1108
|
+
});
|
|
533
1109
|
if (!provider) {
|
|
534
1110
|
log.warn(
|
|
535
1111
|
{
|
|
536
1112
|
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
537
|
-
candidateCount:
|
|
1113
|
+
candidateCount: originalCandidateCount,
|
|
538
1114
|
stableCount: pool.stable.length,
|
|
539
1115
|
finderCount: pool.finder.length,
|
|
540
1116
|
},
|
|
@@ -542,28 +1118,77 @@ export async function selectPool(
|
|
|
542
1118
|
);
|
|
543
1119
|
throw new MemoryV3RetrievalUnavailableError(
|
|
544
1120
|
"memory-v3 pool selector provider unavailable",
|
|
1121
|
+
{ pool, turn },
|
|
545
1122
|
);
|
|
546
1123
|
}
|
|
547
1124
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1125
|
+
const effectiveContextWindow = getEffectiveContextWindow(
|
|
1126
|
+
MEMORY_V3_SELECT_CALL_SITE,
|
|
1127
|
+
{ selectionSeed: turn.conversationId },
|
|
1128
|
+
);
|
|
1129
|
+
const budget = budgetSelectorPool({
|
|
1130
|
+
pool,
|
|
1131
|
+
turn,
|
|
1132
|
+
systemPrompt,
|
|
1133
|
+
provider,
|
|
1134
|
+
model: effectiveContextWindow.model,
|
|
1135
|
+
maxInputTokens: effectiveContextWindow.maxInputTokens,
|
|
1136
|
+
});
|
|
1137
|
+
const budgetedPool = budget.pool;
|
|
1138
|
+
const budgetedTurn = budget.turn;
|
|
1139
|
+
const ordered = orderedLines(budgetedPool);
|
|
1140
|
+
if (
|
|
1141
|
+
ordered.length < originalCandidateCount ||
|
|
1142
|
+
turnContextChars(budgetedTurn) < turnContextChars(turn)
|
|
1143
|
+
) {
|
|
1144
|
+
log.info(
|
|
1145
|
+
{
|
|
1146
|
+
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
1147
|
+
providerName: provider.name,
|
|
1148
|
+
model: effectiveContextWindow.model,
|
|
1149
|
+
maxInputTokens: effectiveContextWindow.maxInputTokens,
|
|
1150
|
+
budgetRatio: SELECTOR_CONTEXT_BUDGET_RATIO,
|
|
1151
|
+
budgetTokens: budget.budgetTokens,
|
|
1152
|
+
originalEstimatedInputTokens: budget.originalEstimatedInputTokens,
|
|
1153
|
+
estimatedInputTokens: budget.estimatedInputTokens,
|
|
1154
|
+
originalStableCount: pool.stable.length,
|
|
1155
|
+
retainedStableCount: budgetedPool.stable.length,
|
|
1156
|
+
originalFinderCount: pool.finder.length,
|
|
1157
|
+
retainedFinderCount: budgetedPool.finder.length,
|
|
1158
|
+
originalTurnContextChars: turnContextChars(turn),
|
|
1159
|
+
retainedTurnContextChars: turnContextChars(budgetedTurn),
|
|
1160
|
+
},
|
|
1161
|
+
"trimmed pool selector input to the context budget",
|
|
1162
|
+
);
|
|
554
1163
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1164
|
+
if (ordered.length === 0) {
|
|
1165
|
+
log.warn(
|
|
1166
|
+
{
|
|
1167
|
+
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
1168
|
+
providerName: provider.name,
|
|
1169
|
+
model: effectiveContextWindow.model,
|
|
1170
|
+
maxInputTokens: effectiveContextWindow.maxInputTokens,
|
|
1171
|
+
candidateCount: originalCandidateCount,
|
|
1172
|
+
},
|
|
1173
|
+
"no pool selector candidate fits the context budget",
|
|
1174
|
+
);
|
|
1175
|
+
throw new MemoryV3RetrievalUnavailableError(
|
|
1176
|
+
"memory-v3 pool selector has no candidate that fits the context budget",
|
|
1177
|
+
{ pool: budgetedPool, turn: budgetedTurn },
|
|
1178
|
+
);
|
|
558
1179
|
}
|
|
559
|
-
|
|
560
|
-
|
|
1180
|
+
|
|
1181
|
+
if (provider.name === TYPE_SAFE_PROVIDER_ID) {
|
|
1182
|
+
return selectPoolWithTypesafe(
|
|
1183
|
+
budgetedPool,
|
|
1184
|
+
budgetedTurn,
|
|
1185
|
+
ordered,
|
|
1186
|
+
systemPrompt,
|
|
1187
|
+
provider,
|
|
1188
|
+
);
|
|
561
1189
|
}
|
|
562
|
-
tailParts.push(`<recent_context>${turn.recentContext}</recent_context>`);
|
|
563
|
-
tailParts.push(`<current_message>${turn.currentMessage}</current_message>`);
|
|
564
|
-
content.push({ type: "text", text: tailParts.join("\n") });
|
|
565
1190
|
|
|
566
|
-
const userMsg
|
|
1191
|
+
const userMsg = buildGenericSelectorMessage(budgetedPool, budgetedTurn);
|
|
567
1192
|
const failures: PoolSelectorAttemptFailure[] = [];
|
|
568
1193
|
let attempt = 0;
|
|
569
1194
|
const recordFailure = (
|
|
@@ -581,8 +1206,8 @@ export async function selectPool(
|
|
|
581
1206
|
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
582
1207
|
providerName: provider.name,
|
|
583
1208
|
candidateCount: ordered.length,
|
|
584
|
-
stableCount:
|
|
585
|
-
finderCount:
|
|
1209
|
+
stableCount: budgetedPool.stable.length,
|
|
1210
|
+
finderCount: budgetedPool.finder.length,
|
|
586
1211
|
};
|
|
587
1212
|
failures.push(diagnostic);
|
|
588
1213
|
log.warn(diagnostic, "pool selector attempt failed");
|
|
@@ -609,6 +1234,7 @@ export async function selectPool(
|
|
|
609
1234
|
config: {
|
|
610
1235
|
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
611
1236
|
conversationId: turn.conversationId,
|
|
1237
|
+
selectionSeed: turn.conversationId,
|
|
612
1238
|
tool_choice: { type: "tool" as const, name: SELECT_PAGES_TOOL_NAME },
|
|
613
1239
|
// The last block of this one-shot message varies every turn; the
|
|
614
1240
|
// provider's auto-applied turn-start breakpoint would land on it and
|
|
@@ -675,8 +1301,8 @@ export async function selectPool(
|
|
|
675
1301
|
log.warn(
|
|
676
1302
|
{
|
|
677
1303
|
candidateCount: ordered.length,
|
|
678
|
-
stableCount:
|
|
679
|
-
finderCount:
|
|
1304
|
+
stableCount: budgetedPool.stable.length,
|
|
1305
|
+
finderCount: budgetedPool.finder.length,
|
|
680
1306
|
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
681
1307
|
providerName: provider.name,
|
|
682
1308
|
failures,
|
|
@@ -688,14 +1314,16 @@ export async function selectPool(
|
|
|
688
1314
|
{
|
|
689
1315
|
cause: lastError,
|
|
690
1316
|
conversationNotice: providerBillingNoticeFromError(lastError),
|
|
1317
|
+
pool: budgetedPool,
|
|
1318
|
+
turn: budgetedTurn,
|
|
691
1319
|
},
|
|
692
1320
|
);
|
|
693
1321
|
}
|
|
694
1322
|
log.warn(
|
|
695
1323
|
{
|
|
696
1324
|
candidateCount: ordered.length,
|
|
697
|
-
stableCount:
|
|
698
|
-
finderCount:
|
|
1325
|
+
stableCount: budgetedPool.stable.length,
|
|
1326
|
+
finderCount: budgetedPool.finder.length,
|
|
699
1327
|
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
700
1328
|
providerName: provider.name,
|
|
701
1329
|
failures,
|
|
@@ -704,12 +1332,18 @@ export async function selectPool(
|
|
|
704
1332
|
);
|
|
705
1333
|
throw new MemoryV3RetrievalUnavailableError(
|
|
706
1334
|
"memory-v3 pool selector returned no usable selection after retries",
|
|
1335
|
+
{ pool: budgetedPool, turn: budgetedTurn },
|
|
707
1336
|
);
|
|
708
1337
|
}
|
|
709
1338
|
|
|
710
1339
|
// Omitted `ids` is the recall-safe "keep all candidates" signal.
|
|
711
1340
|
if (parsed.ids === undefined) {
|
|
712
|
-
return {
|
|
1341
|
+
return {
|
|
1342
|
+
pages: selectAllPoolCandidates(budgetedPool),
|
|
1343
|
+
keptAll: true,
|
|
1344
|
+
pool: budgetedPool,
|
|
1345
|
+
turn: budgetedTurn,
|
|
1346
|
+
};
|
|
713
1347
|
}
|
|
714
1348
|
|
|
715
1349
|
// Map 1-based IDs over the concatenated numbering, dropping out-of-range
|
|
@@ -717,5 +1351,10 @@ export async function selectPool(
|
|
|
717
1351
|
const picked = parsed.ids
|
|
718
1352
|
.filter((id) => id >= 1 && id <= ordered.length)
|
|
719
1353
|
.map((id) => id - 1);
|
|
720
|
-
return {
|
|
1354
|
+
return {
|
|
1355
|
+
pages: mergeSelectedLines(ordered, picked),
|
|
1356
|
+
keptAll: false,
|
|
1357
|
+
pool: budgetedPool,
|
|
1358
|
+
turn: budgetedTurn,
|
|
1359
|
+
};
|
|
721
1360
|
}
|