@vellumai/assistant 0.8.6 → 0.8.7
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/AGENTS.md +4 -4
- package/Dockerfile +1 -0
- package/bun.lock +11 -2
- package/docker-entrypoint.sh +8 -6
- package/docs/plugins.md +63 -28
- package/examples/plugins/echo/register.ts +4 -7
- package/knip.json +1 -0
- package/node_modules/@vellumai/environments/bun.lock +24 -0
- package/node_modules/@vellumai/environments/package.json +18 -0
- package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +95 -0
- package/node_modules/@vellumai/environments/src/index.ts +11 -0
- package/node_modules/@vellumai/environments/src/seeds.ts +73 -0
- package/node_modules/@vellumai/environments/src/types.ts +70 -0
- package/node_modules/@vellumai/environments/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +11 -0
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +3 -4
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -2
- package/openapi.yaml +3735 -353
- package/package.json +7 -3
- package/scripts/generate-openapi.ts +20 -13
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +42 -80
- package/src/__tests__/agent-loop-exit-reason.test.ts +240 -39
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +141 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +19 -32
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +6 -4
- package/src/__tests__/agent-loop-thinking.test.ts +17 -12
- package/src/__tests__/agent-loop.test.ts +207 -341
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +4 -2
- package/src/__tests__/agent-wake-override-profile.test.ts +22 -40
- package/src/__tests__/anthropic-provider.test.ts +201 -55
- package/src/__tests__/app-builder-skill-instructions.test.ts +22 -0
- package/src/__tests__/app-control-flow.test.ts +5 -0
- package/src/__tests__/approval-cascade.test.ts +4 -11
- package/src/__tests__/approval-routes-http.test.ts +4 -2
- package/src/__tests__/assistant-event.test.ts +15 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/avatar-e2e.test.ts +7 -37
- package/src/__tests__/avatar-generator.test.ts +12 -42
- package/src/__tests__/avatar-identity-sync.test.ts +28 -3
- package/src/__tests__/background-shell-bash.test.ts +3 -7
- package/src/__tests__/btw-routes.test.ts +7 -12
- package/src/__tests__/call-pointer-messages.test.ts +5 -3
- package/src/__tests__/call-site-routing-provider.test.ts +22 -40
- package/src/__tests__/catalog-files.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +48 -20
- package/src/__tests__/channel-approvals.test.ts +3 -1
- package/src/__tests__/channel-invite-transport.test.ts +1 -5
- package/src/__tests__/channel-readiness-routes.test.ts +0 -4
- package/src/__tests__/channel-readiness-slack-remote.test.ts +2 -7
- package/src/__tests__/channel-retry-sweep.test.ts +71 -79
- package/src/__tests__/circuit-breaker-pipeline.test.ts +3 -3
- package/src/__tests__/clawhub-files.test.ts +1 -0
- package/src/__tests__/compaction-events.test.ts +5 -17
- package/src/__tests__/compaction-pipeline.test.ts +1 -1
- package/src/__tests__/compaction-timeout-recovery.test.ts +37 -48
- package/src/__tests__/compaction-trail-store.test.ts +1 -79
- package/src/__tests__/compactor-image-manifest-trust.test.ts +112 -0
- package/src/__tests__/computer-use-tools.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +28 -0
- package/src/__tests__/context-search-agent-runner.test.ts +6 -3
- package/src/__tests__/context-token-estimator.test.ts +34 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +291 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +14 -7
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +12 -27
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +430 -90
- package/src/__tests__/conversation-agent-loop.test.ts +581 -62
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -3
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clear-safety.test.ts +20 -10
- package/src/__tests__/conversation-confirmation-signals.test.ts +15 -45
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-disk-view.test.ts +10 -17
- package/src/__tests__/conversation-fork-crud.test.ts +86 -172
- package/src/__tests__/conversation-fork-route.test.ts +16 -14
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -6
- package/src/__tests__/conversation-lifecycle.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +3 -2
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -1
- package/src/__tests__/conversation-message-sync-tags.test.ts +3 -4
- package/src/__tests__/conversation-pairing.test.ts +34 -4
- package/src/__tests__/conversation-pre-run-repair.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -0
- package/src/__tests__/conversation-process-callsite.test.ts +27 -30
- package/src/__tests__/conversation-provider-retry-repair.test.ts +53 -44
- package/src/__tests__/conversation-queue.test.ts +270 -164
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -2
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +20 -22
- package/src/__tests__/conversation-runtime-workspace.test.ts +19 -1
- package/src/__tests__/conversation-slash-queue.test.ts +37 -31
- package/src/__tests__/conversation-slash-unknown.test.ts +13 -15
- package/src/__tests__/conversation-speed-override.test.ts +8 -22
- package/src/__tests__/conversation-stream-state.test.ts +484 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +6 -15
- package/src/__tests__/conversation-surfaces-app-control.test.ts +32 -4
- package/src/__tests__/conversation-surfaces-state-update.test.ts +5 -2
- package/src/__tests__/conversation-surfaces-table-action.test.ts +6 -15
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +23 -11
- package/src/__tests__/conversation-unread-route.test.ts +14 -2
- package/src/__tests__/conversation-usage.test.ts +0 -2
- package/src/__tests__/conversation-wipe.test.ts +1 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +3 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +48 -22
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +27 -7
- package/src/__tests__/credential-execution-tools.test.ts +1 -2
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/cross-provider-web-search.test.ts +6 -2
- package/src/__tests__/cu-unified-flow.test.ts +26 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +11 -0
- package/src/__tests__/disk-pressure-guard.test.ts +66 -0
- package/src/__tests__/disk-pressure-routes.test.ts +9 -2
- package/src/__tests__/dm-persistence.test.ts +7 -2
- package/src/__tests__/dynamic-page-surface.test.ts +68 -0
- package/src/__tests__/edit-propagation.test.ts +1 -2
- package/src/__tests__/empty-response-pipeline.test.ts +127 -5
- package/src/__tests__/filing-service.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +55 -14
- package/src/__tests__/gemini-inline-media.test.ts +78 -0
- package/src/__tests__/gemini-provider.test.ts +351 -28
- package/src/__tests__/guardian-routing-state.test.ts +60 -71
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +9 -7
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +2 -1
- package/src/__tests__/history-repair-hook.test.ts +161 -0
- package/src/__tests__/history-repair-observability.test.ts +1 -1
- package/src/__tests__/history-repair.test.ts +2 -1
- package/src/__tests__/host-app-control-proxy.test.ts +2 -0
- package/src/__tests__/host-cu-proxy.test.ts +2 -0
- package/src/__tests__/host-file-edit-tool.test.ts +4 -2
- package/src/__tests__/host-file-proxy.test.ts +31 -0
- package/src/__tests__/host-file-read-tool.test.ts +4 -2
- package/src/__tests__/host-file-write-tool.test.ts +9 -3
- package/src/__tests__/host-proxy-preactivation.test.ts +53 -14
- package/src/__tests__/host-shell-tool.test.ts +9 -4
- package/src/__tests__/http-user-message-parity.test.ts +2 -2
- package/src/__tests__/identity-intro-cache.test.ts +35 -14
- package/src/__tests__/inbound-slack-persistence.test.ts +7 -2
- package/src/__tests__/injector-background-turn.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +1 -1
- package/src/__tests__/injector-disk-pressure.test.ts +1 -1
- package/src/__tests__/injector-document-comments.test.ts +1 -1
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +1 -1
- package/src/__tests__/injector-v3-suppression.test.ts +220 -0
- package/src/__tests__/list-messages-attachments.test.ts +7 -8
- package/src/__tests__/list-messages-hidden-metadata.test.ts +17 -15
- package/src/__tests__/list-messages-page-latest.test.ts +0 -1
- package/src/__tests__/list-messages-tool-merge.test.ts +36 -6
- package/src/__tests__/llm-call-pipeline.test.ts +21 -15
- package/src/__tests__/llm-request-log-turn-query.test.ts +42 -86
- package/src/__tests__/llm-resolver.test.ts +23 -47
- package/src/__tests__/llm-usage-store.test.ts +45 -0
- package/src/__tests__/log-export-routes.test.ts +59 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -8
- package/src/__tests__/mcp-auth-routes.test.ts +15 -10
- package/src/__tests__/mcp-health-check.test.ts +18 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +1 -1
- package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +8 -4
- package/src/__tests__/migration-export-http.test.ts +12 -12
- package/src/__tests__/migration-import-commit-http.test.ts +8 -8
- package/src/__tests__/migration-import-preflight-http.test.ts +7 -7
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +14 -20
- package/src/__tests__/notification-decision-identity.test.ts +9 -18
- package/src/__tests__/notification-decision-recipient-context.test.ts +3 -6
- package/src/__tests__/oauth-commands-routes.test.ts +1 -1
- package/src/__tests__/onboarding-template-contract.test.ts +10 -0
- package/src/__tests__/openai-provider.test.ts +66 -70
- package/src/__tests__/openai-responses-provider.test.ts +21 -77
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -1
- package/src/__tests__/overflow-reduce-pipeline.test.ts +2 -4
- package/src/__tests__/parallel-tool.benchmark.test.ts +24 -36
- package/src/__tests__/persistence-pipeline.test.ts +15 -26
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -1
- package/src/__tests__/pipeline-runner.test.ts +2 -3
- package/src/__tests__/plugin-bootstrap.test.ts +51 -25
- package/src/__tests__/plugin-route-contribution.test.ts +6 -16
- package/src/__tests__/plugin-skill-contribution.test.ts +7 -17
- package/src/__tests__/plugin-tool-contribution.test.ts +10 -26
- package/src/__tests__/plugin-types.test.ts +7 -14
- package/src/__tests__/prechat-onboarding-contract.test.ts +23 -0
- package/src/__tests__/process-message-background-slack.test.ts +17 -16
- package/src/__tests__/process-message-display-content.test.ts +30 -42
- package/src/__tests__/provider-commit-message-generator.test.ts +19 -14
- package/src/__tests__/provider-error-scenarios.test.ts +7 -6
- package/src/__tests__/provider-platform-proxy-integration.test.ts +3 -8
- package/src/__tests__/provider-send-message-override-profile.test.ts +9 -25
- package/src/__tests__/provider-streaming.benchmark.test.ts +12 -22
- package/src/__tests__/provider-usage-tracking.test.ts +0 -6
- package/src/__tests__/ratelimit.test.ts +9 -4
- package/src/__tests__/relay-server.test.ts +20 -13
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +5 -8
- package/src/__tests__/retry-thinking-tool-choice.test.ts +10 -13
- package/src/__tests__/retry-verbosity-normalization.test.ts +5 -8
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +353 -0
- package/src/__tests__/schedule-routes.test.ts +80 -10
- package/src/__tests__/schedule-store.test.ts +67 -0
- package/src/__tests__/schedule-tools.test.ts +125 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -2
- package/src/__tests__/secret-prompt-log-hygiene.test.ts +11 -7
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +11 -9
- package/src/__tests__/secret-response-routing.test.ts +13 -11
- package/src/__tests__/send-endpoint-busy.test.ts +2 -1
- package/src/__tests__/shell-observability.test.ts +249 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +11 -11
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +10 -10
- package/src/__tests__/skills-files-catalog-fallback.test.ts +10 -0
- package/src/__tests__/skillssh-files.test.ts +1 -0
- package/src/__tests__/starter-task-flow.test.ts +6 -6
- package/src/__tests__/strip-memory-injections.test.ts +102 -14
- package/src/__tests__/subagent-call-site-routing.test.ts +2 -2
- package/src/__tests__/suggestion-routes.test.ts +3 -3
- package/src/__tests__/sync-message-contract.test.ts +19 -16
- package/src/__tests__/system-prompt.test.ts +54 -0
- package/src/__tests__/terminal-tools.test.ts +3 -24
- package/src/__tests__/thread-backfill.test.ts +4 -9
- package/src/__tests__/title-generate-pipeline.test.ts +1 -1
- package/src/__tests__/token-estimate-pipeline.test.ts +2 -4
- package/src/__tests__/tool-error-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execute-pipeline.test.ts +1 -1
- package/src/__tests__/tool-preview-lifecycle.test.ts +13 -11
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +9 -12
- package/src/__tests__/tool-result-truncation.test.ts +3 -1
- package/src/__tests__/tools-audio-read.test.ts +113 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +44 -84
- package/src/__tests__/turn-events-store.test.ts +11 -7
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +8 -6
- package/src/__tests__/voice-session-bridge.test.ts +13 -7
- package/src/acp/__tests__/prepare-agent-env.test.ts +143 -31
- package/src/acp/prepare-agent-env.ts +52 -11
- package/src/agent/compaction-circuit.ts +140 -0
- package/src/agent/loop.ts +409 -85
- package/src/api/README.md +19 -17
- package/src/api/constants/tool-execution.ts +21 -0
- package/src/api/events/assistant-activity-state.ts +75 -0
- package/src/api/events/assistant-outbound-attachment.ts +25 -27
- package/src/api/events/assistant-text-delta.ts +6 -8
- package/src/api/events/assistant-turn-start.ts +5 -7
- package/src/api/events/avatar-updated.ts +24 -0
- package/src/api/events/compaction-circuit-closed.ts +26 -0
- package/src/api/events/compaction-circuit-open.ts +28 -0
- package/src/api/events/confirmation-request.ts +114 -0
- package/src/api/events/contact-request.ts +33 -0
- package/src/api/events/conversation-error.ts +77 -0
- package/src/api/events/conversation-list-invalidated.ts +38 -0
- package/src/api/events/conversation-title-updated.ts +24 -0
- package/src/api/events/disk-pressure-status-changed.ts +61 -0
- package/src/api/events/document-comment-created.ts +24 -28
- package/src/api/events/document-comment-deleted.ts +6 -8
- package/src/api/events/document-comment-reopened.ts +6 -8
- package/src/api/events/document-comment-resolved.ts +8 -10
- package/src/api/events/document-editor-update.ts +27 -0
- package/src/api/events/error.ts +32 -0
- package/src/api/events/generation-cancelled.ts +4 -6
- package/src/api/events/generation-handoff.ts +13 -15
- package/src/api/events/home-feed-updated.ts +26 -0
- package/src/api/events/identity-changed.ts +32 -0
- package/src/api/events/interaction-resolved.ts +50 -0
- package/src/api/events/message-complete.ts +10 -12
- package/src/api/events/message-dequeued.ts +21 -0
- package/src/api/events/message-queued-deleted.ts +23 -0
- package/src/api/events/message-queued.ts +22 -0
- package/src/api/events/message-request-complete.ts +29 -0
- package/src/api/events/navigate-settings.ts +20 -0
- package/src/api/events/notification-intent.ts +33 -0
- package/src/api/events/open-url.ts +6 -8
- package/src/api/events/question-request.ts +67 -0
- package/src/api/events/relationship-state-updated.ts +4 -6
- package/src/api/events/secret-request.ts +42 -0
- package/src/api/events/subagent-event.ts +79 -0
- package/src/api/events/subagent-spawned.ts +40 -0
- package/src/api/events/subagent-status-changed.ts +65 -0
- package/src/api/events/sync-changed.ts +29 -0
- package/src/api/events/tool-result.ts +129 -0
- package/src/api/events/tool-use-start.ts +8 -10
- package/src/api/events/turn-profile-auto-routed.ts +28 -0
- package/src/api/events/ui-surface-complete.ts +30 -0
- package/src/api/events/ui-surface-dismiss.ts +22 -0
- package/src/api/events/ui-surface-show.ts +67 -0
- package/src/api/events/ui-surface-update.ts +26 -0
- package/src/api/events/usage-update.ts +34 -0
- package/src/api/events/user-message-echo.ts +35 -0
- package/src/api/index.ts +354 -0
- package/src/api/requests/dictation.ts +45 -0
- package/src/api/responses/disk-pressure-status.ts +26 -0
- package/src/api/responses/home.ts +217 -0
- package/src/api/responses/llm-context-response.ts +2 -0
- package/src/api/responses/memory-v3-selection-log.ts +50 -0
- package/src/api/responses/subagent-detail.ts +48 -0
- package/src/approvals/guardian-decision-primitive.ts +7 -15
- package/src/approvals/guardian-request-resolvers.ts +6 -9
- package/src/avatar/__tests__/avatar-manifest.test.ts +236 -0
- package/src/avatar/__tests__/avatar-store.test.ts +193 -0
- package/src/avatar/avatar-manifest.ts +195 -0
- package/src/avatar/avatar-store.ts +113 -0
- package/src/avatar/traits-png-sync.ts +8 -2
- package/src/background-wake/next-wake.test.ts +31 -1
- package/src/background-wake/next-wake.ts +4 -1
- package/src/calls/call-conversation-messages.ts +6 -4
- package/src/calls/guardian-action-sweep.ts +6 -4
- package/src/calls/relay-server.ts +12 -8
- package/src/calls/voice-session-bridge.ts +13 -27
- package/src/cli/commands/__tests__/memory-v3.test.ts +245 -0
- package/src/cli/commands/avatar.ts +17 -11
- package/src/cli/commands/conversations.ts +15 -1
- package/src/cli/commands/db/__tests__/repair.test.ts +540 -0
- package/src/cli/commands/db/__tests__/status.test.ts +253 -0
- package/src/cli/commands/db/format.ts +48 -0
- package/src/cli/commands/db/index.ts +29 -0
- package/src/cli/commands/db/repair-step-conversation-backfill.ts +345 -0
- package/src/cli/commands/db/repair-step-integrity.ts +146 -0
- package/src/cli/commands/db/repair-steps.ts +164 -0
- package/src/cli/commands/db/repair.ts +141 -0
- package/src/cli/commands/db/status.ts +366 -0
- package/src/cli/commands/memory-v3.ts +159 -445
- package/src/cli/lib/cli-colors.ts +24 -6
- package/src/cli/program.ts +4 -5
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/assistant-feature-flags.ts +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +14 -3
- package/src/config/bundled-skills/media-processing/services/reduce.ts +6 -9
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +7 -2
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/call-site-defaults.ts +2 -7
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -226
- package/src/config/schemas/call-site-catalog.ts +8 -15
- package/src/config/schemas/llm.ts +2 -3
- package/src/config/schemas/memory-lifecycle.ts +24 -0
- package/src/config/schemas/memory-v2.ts +0 -253
- package/src/config/schemas/memory-v3.ts +39 -0
- package/src/config/schemas/memory.ts +6 -1
- package/src/config/schemas/timeouts.ts +3 -1
- package/src/context/compactor.ts +54 -31
- package/src/context/token-estimator.ts +19 -0
- package/src/context/tool-result-truncation.ts +1 -43
- package/src/context/window-manager.ts +138 -20
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +2 -2
- package/src/daemon/__tests__/web-search-status-text.test.ts +10 -6
- package/src/daemon/approval-generators.ts +4 -4
- package/src/daemon/config-watcher.ts +7 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +225 -88
- package/src/daemon/conversation-agent-loop.ts +284 -584
- package/src/daemon/conversation-error.ts +7 -7
- package/src/daemon/conversation-history.ts +22 -6
- package/src/daemon/conversation-launch.ts +4 -8
- package/src/daemon/conversation-lifecycle.ts +10 -38
- package/src/daemon/conversation-messaging.ts +1 -3
- package/src/daemon/conversation-notifiers.ts +7 -5
- package/src/daemon/conversation-process.ts +100 -79
- package/src/daemon/conversation-runtime-assembly.ts +47 -21
- package/src/daemon/conversation-store.ts +6 -5
- package/src/daemon/conversation-surfaces.ts +55 -69
- package/src/daemon/conversation-tool-setup.ts +3 -0
- package/src/daemon/conversation.ts +91 -126
- package/src/daemon/daemon-skill-host.ts +2 -6
- package/src/daemon/disk-pressure-guard.ts +35 -29
- package/src/daemon/external-plugins-bootstrap.ts +46 -24
- package/src/daemon/first-greeting.ts +26 -4
- package/src/daemon/guardian-action-generators.ts +2 -2
- package/src/daemon/handlers/conversations.ts +6 -22
- package/src/daemon/handlers/shared.ts +4 -0
- package/src/daemon/handlers/skills.ts +15 -14
- package/src/daemon/host-app-control-proxy.ts +54 -1
- package/src/daemon/host-cu-proxy.ts +46 -22
- package/src/daemon/host-file-proxy.ts +25 -1
- package/src/daemon/host-proxy-preactivation.ts +25 -6
- package/src/daemon/lifecycle.ts +28 -55
- package/src/daemon/message-protocol.ts +2 -3
- package/src/daemon/message-provenance.ts +49 -0
- package/src/daemon/message-types/contacts.ts +3 -20
- package/src/daemon/message-types/conversations.ts +13 -111
- package/src/daemon/message-types/documents.ts +3 -9
- package/src/daemon/message-types/home.ts +4 -17
- package/src/daemon/message-types/integrations.ts +2 -6
- package/src/daemon/message-types/messages.ts +28 -343
- package/src/daemon/message-types/notifications.ts +2 -32
- package/src/daemon/message-types/settings.ts +3 -8
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/sync.ts +12 -25
- package/src/daemon/message-types/workspace.ts +3 -11
- package/src/daemon/process-message.ts +49 -46
- package/src/daemon/server.ts +12 -0
- package/src/daemon/tool-side-effects.ts +10 -7
- package/src/daemon/trust-context.ts +13 -0
- package/src/daemon/wake-target-adapter.ts +11 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -1
- package/src/heartbeat/heartbeat-run-store.ts +31 -0
- package/src/heartbeat/heartbeat-service.ts +16 -0
- package/src/home/feature-gate.ts +22 -0
- package/src/home/feed-types.ts +36 -221
- package/src/ipc/__tests__/email-ipc.test.ts +0 -9
- package/src/ipc/routes/__tests__/route-adapter.test.ts +244 -0
- package/src/ipc/routes/route-adapter.ts +45 -6
- package/src/ipc/skill-routes/__tests__/memory.test.ts +18 -9
- package/src/ipc/skill-routes/__tests__/providers.test.ts +10 -10
- package/src/ipc/skill-routes/__tests__/registries.test.ts +28 -18
- package/src/ipc/skill-routes/memory.ts +26 -13
- package/src/ipc/skill-routes/providers.ts +5 -6
- package/src/ipc/skill-routes/registries.ts +13 -61
- package/src/live-voice/__tests__/live-voice-archive.test.ts +24 -11
- package/src/memory/__tests__/conversation-queries.test.ts +192 -8
- package/src/memory/__tests__/db-maintenance.test.ts +128 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +5 -4
- package/src/memory/__tests__/memory-retrospective-job.test.ts +10 -6
- package/src/memory/__tests__/memory-v3-selections-migration.test.ts +103 -0
- package/src/memory/context-search/agent-runner.ts +2 -4
- package/src/memory/conversation-crud.ts +39 -8
- package/src/memory/conversation-queries.ts +78 -22
- package/src/memory/db-init.ts +8 -0
- package/src/memory/db-maintenance.ts +18 -2
- package/src/memory/graph/consolidation.ts +8 -11
- package/src/memory/graph/conversation-graph-memory.ts +41 -8
- package/src/memory/graph/extraction.ts +6 -9
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.ts +20 -26
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/job-handlers/conversation-starters.ts +32 -32
- package/src/memory/job-handlers/summarization.ts +1 -2
- package/src/memory/jobs-store.ts +3 -1
- package/src/memory/jobs-worker.ts +51 -39
- package/src/memory/llm-request-log-source-clickhouse.ts +5 -31
- package/src/memory/llm-request-log-source-local.ts +0 -11
- package/src/memory/llm-request-log-source.ts +9 -25
- package/src/memory/llm-request-log-store.ts +0 -41
- package/src/memory/llm-usage-store.ts +10 -0
- package/src/memory/memory-marker.ts +17 -0
- package/src/memory/memory-retrospective-job.ts +6 -2
- package/src/memory/memory-v2-activation-log-store.ts +1 -83
- package/src/memory/migrations/267-llm-usage-events-add-assistant-version.ts +46 -0
- package/src/memory/migrations/268-add-memory-v3-selections.ts +28 -0
- package/src/memory/migrations/269-schedule-script-timeout.ts +11 -0
- package/src/memory/migrations/270-messages-role-created-at-index.ts +18 -0
- package/src/memory/migrations/__tests__/267-llm-usage-events-add-assistant-version.test.ts +117 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/schema/infrastructure.ts +11 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +124 -0
- package/src/memory/v2/__tests__/migration.test.ts +11 -3
- package/src/memory/v2/__tests__/page-index.test.ts +37 -1
- package/src/memory/v2/__tests__/router.test.ts +14 -4
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -5
- package/src/memory/v2/backfill-jobs.ts +6 -0
- package/src/memory/v2/consolidation-job.ts +89 -9
- package/src/memory/v2/migration.ts +5 -3
- package/src/memory/v2/page-index.ts +11 -0
- package/src/memory/v2/router.ts +8 -11
- package/src/memory/v2/sweep-job.ts +8 -11
- package/src/memory/v2/types.ts +1 -0
- package/src/memory/v3/__tests__/assign.test.ts +242 -0
- package/src/memory/v3/__tests__/capabilities.test.ts +118 -0
- package/src/memory/v3/__tests__/core.test.ts +39 -0
- package/src/memory/v3/__tests__/fixtures/eval-turns.json +36 -0
- package/src/memory/v3/__tests__/fixtures/live-turns.json +37 -0
- package/src/memory/v3/__tests__/health.test.ts +203 -0
- package/src/memory/v3/__tests__/live-integration.test.ts +330 -0
- package/src/memory/v3/__tests__/maintain-job.test.ts +288 -0
- package/src/memory/v3/__tests__/needle.test.ts +107 -0
- package/src/memory/v3/__tests__/orchestrate.test.ts +400 -0
- package/src/memory/v3/__tests__/reconcile.test.ts +274 -0
- package/src/memory/v3/__tests__/render-injection.test.ts +61 -0
- package/src/memory/v3/__tests__/router.test.ts +260 -0
- package/src/memory/v3/__tests__/selection-log-store.test.ts +179 -0
- package/src/memory/v3/__tests__/selector.test.ts +404 -0
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +414 -0
- package/src/memory/v3/__tests__/snapshot.test.ts +168 -0
- package/src/memory/v3/__tests__/tree.test.ts +192 -0
- package/src/memory/v3/__tests__/types.test.ts +54 -0
- package/src/memory/v3/__tests__/working-set-eviction.test.ts +106 -0
- package/src/memory/v3/__tests__/working-set-skeleton.test.ts +44 -0
- package/src/memory/v3/assign.ts +268 -0
- package/src/memory/v3/capabilities.ts +124 -0
- package/src/memory/v3/core.ts +26 -0
- package/src/memory/v3/data/README.md +84 -0
- package/src/memory/v3/data/assignments.json +5 -0
- package/src/memory/v3/data/core.json +1 -0
- package/src/memory/v3/data/leaves/domain-a/topic-x.md +9 -0
- package/src/memory/v3/data/leaves/domain-a/topic-y.md +9 -0
- package/src/memory/v3/data/leaves/domain-b/topic-z.md +9 -0
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/maintain-job.ts +314 -0
- package/src/memory/v3/needle.ts +115 -0
- package/src/memory/v3/orchestrate.ts +114 -0
- package/src/memory/v3/page-content.ts +34 -0
- package/src/memory/v3/provider-blocks.ts +16 -0
- package/src/memory/v3/reconcile.ts +523 -0
- package/src/memory/v3/render-injection.ts +32 -0
- package/src/memory/v3/router.ts +184 -0
- package/src/memory/v3/selection-log-store.ts +84 -0
- package/src/memory/v3/selector.ts +211 -0
- package/src/memory/v3/shadow-plugin.ts +379 -0
- package/src/memory/v3/snapshot.ts +209 -0
- package/src/memory/v3/tree.ts +174 -0
- package/src/memory/v3/types.ts +46 -60
- package/src/memory/v3/working-set.ts +88 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1 -1
- package/src/messaging/providers/slack/render-transcript.ts +2 -2
- package/src/messaging/style-analyzer.ts +8 -11
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/decision-engine.ts +10 -13
- package/src/notifications/preference-extractor.ts +11 -14
- package/src/permissions/prompter.ts +42 -36
- package/src/permissions/question-prompter.test.ts +35 -26
- package/src/permissions/question-prompter.ts +6 -10
- package/src/plugin-api/index.ts +2 -0
- package/src/plugin-api/types.ts +25 -3
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
- package/src/plugins/defaults/circuit-breaker/package.json +15 -0
- package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
- package/src/plugins/defaults/compaction/package.json +15 -0
- package/src/plugins/defaults/compaction/register.ts +35 -0
- package/src/plugins/defaults/compaction/terminal.ts +73 -0
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +22 -0
- package/src/plugins/defaults/empty-response/package.json +15 -0
- package/src/plugins/defaults/empty-response/register.ts +28 -0
- package/src/plugins/defaults/empty-response/terminal.ts +106 -0
- package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/history-repair/package.json +15 -0
- package/src/plugins/defaults/history-repair/register.ts +24 -0
- package/src/{daemon/history-repair.ts → plugins/defaults/history-repair/terminal.ts} +48 -35
- package/src/plugins/defaults/index.ts +29 -40
- package/src/plugins/defaults/injectors/package.json +15 -0
- package/src/plugins/defaults/{injectors.ts → injectors/register.ts} +14 -38
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +17 -0
- package/src/plugins/defaults/llm-call/package.json +15 -0
- package/src/plugins/defaults/{llm-call.ts → llm-call/register.ts} +6 -38
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +17 -0
- package/src/plugins/defaults/memory-retrieval/package.json +15 -0
- package/src/plugins/defaults/{memory-retrieval.ts → memory-retrieval/register.ts} +10 -48
- package/src/plugins/defaults/{overflow-reduce.ts → overflow-reduce/middlewares/overflowReduce.ts} +18 -77
- package/src/plugins/defaults/overflow-reduce/package.json +15 -0
- package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
- package/src/plugins/defaults/persistence/middlewares/persistence.ts +19 -0
- package/src/plugins/defaults/persistence/package.json +15 -0
- package/src/plugins/defaults/persistence/register.ts +38 -0
- package/src/plugins/defaults/persistence/terminal.ts +83 -0
- package/src/plugins/defaults/title-generate/package.json +15 -0
- package/src/plugins/defaults/title-generate/register.ts +35 -0
- package/src/plugins/defaults/title-generate/terminal.ts +31 -0
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +23 -0
- package/src/plugins/defaults/token-estimate/package.json +15 -0
- package/src/plugins/defaults/token-estimate/register.ts +34 -0
- package/src/plugins/defaults/token-estimate/terminal.ts +40 -0
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +21 -0
- package/src/plugins/defaults/tool-error/package.json +15 -0
- package/src/plugins/defaults/tool-error/register.ts +35 -0
- package/src/plugins/defaults/tool-error/terminal.ts +47 -0
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +23 -0
- package/src/plugins/defaults/tool-execute/package.json +15 -0
- package/src/plugins/defaults/{tool-execute.ts → tool-execute/register.ts} +8 -46
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +23 -0
- package/src/plugins/defaults/tool-result-truncate/package.json +15 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +35 -0
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +113 -0
- package/src/plugins/defaults/tool-result-truncate/types.ts +22 -0
- package/src/plugins/external-plugin-loader.ts +2 -2
- package/src/plugins/pipeline.ts +0 -12
- package/src/plugins/types.ts +51 -90
- package/src/plugins/user-loader.ts +4 -3
- package/src/proactive-artifact/aux-message-injector.ts +0 -1
- package/src/proactive-artifact/job.test.ts +20 -8
- package/src/proactive-artifact/job.ts +3 -1
- package/src/prompts/sections.ts +20 -7
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +2 -2
- package/src/prompts/templates/BOOTSTRAP.md +5 -1
- package/src/prompts/templates/system-sections.ts +6 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -25
- package/src/providers/__tests__/satellite-connection-routing.test.ts +7 -21
- package/src/providers/anthropic/client.ts +24 -5
- package/src/providers/call-site-routing.ts +1 -9
- package/src/providers/gemini/client.ts +152 -34
- package/src/providers/gemini/inline-media.ts +74 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +0 -2
- package/src/providers/openai/chat-completions-provider.ts +1 -4
- package/src/providers/openai/responses-provider.ts +1 -4
- package/src/providers/openrouter/client.ts +1 -6
- package/src/providers/provider-send-message.ts +6 -6
- package/src/providers/ratelimit.ts +1 -9
- package/src/providers/retry.ts +0 -5
- package/src/providers/types.ts +11 -2
- package/src/providers/usage-tracking.ts +1 -9
- package/src/runtime/__tests__/agent-wake.test.ts +131 -26
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -3
- package/src/runtime/agent-wake.ts +93 -18
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +75 -109
- package/src/runtime/auth/__tests__/route-policy.test.ts +153 -170
- package/src/runtime/auth/route-policy.ts +42 -1079
- package/src/runtime/background-job-runner.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -1
- package/src/runtime/channel-approvals.ts +3 -14
- package/src/runtime/channel-invite-transport.ts +5 -6
- package/src/runtime/channel-readiness-service.ts +2 -5
- package/src/runtime/channel-retry-sweep.ts +12 -16
- package/src/runtime/conversation-stream-state.ts +294 -0
- package/src/runtime/http-router.ts +19 -22
- package/src/runtime/http-types.ts +12 -6
- package/src/runtime/invite-instruction-generator.ts +3 -3
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +565 -0
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +4 -4
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +62 -32
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +237 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -22
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +7 -2
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +6 -6
- package/src/runtime/routes/__tests__/stt-routes.test.ts +3 -3
- package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +5 -2
- package/src/runtime/routes/__tests__/tts-routes.test.ts +3 -3
- package/src/runtime/routes/acp-routes.test.ts +97 -75
- package/src/runtime/routes/acp-routes.ts +29 -6
- package/src/runtime/routes/app-management-routes.ts +97 -24
- package/src/runtime/routes/app-routes.ts +25 -5
- package/src/runtime/routes/approval-routes.ts +16 -4
- package/src/runtime/routes/attachment-routes.ts +25 -1
- package/src/runtime/routes/audio-routes.ts +1 -0
- package/src/runtime/routes/audit-routes.ts +5 -0
- package/src/runtime/routes/auth-routes.ts +5 -0
- package/src/runtime/routes/avatar-routes.ts +238 -59
- package/src/runtime/routes/background-tool-routes.ts +9 -0
- package/src/runtime/routes/background-wake-routes.ts +13 -3
- package/src/runtime/routes/backup-routes.ts +45 -0
- package/src/runtime/routes/bookmark-routes.ts +13 -0
- package/src/runtime/routes/brain-graph-routes.ts +9 -0
- package/src/runtime/routes/browser-routes.ts +5 -0
- package/src/runtime/routes/browser-tabs-routes.ts +5 -0
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/cache-routes.ts +13 -0
- package/src/runtime/routes/call-routes.ts +21 -10
- package/src/runtime/routes/channel-availability-routes.ts +5 -1
- package/src/runtime/routes/channel-readiness-routes.ts +37 -4
- package/src/runtime/routes/channel-route-definitions.ts +21 -0
- package/src/runtime/routes/channel-verification-routes.ts +21 -0
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +9 -2
- package/src/runtime/routes/client-routes.ts +9 -0
- package/src/runtime/routes/consolidation-routes.ts +13 -5
- package/src/runtime/routes/contact-prompt-routes.ts +9 -0
- package/src/runtime/routes/contact-routes.ts +90 -23
- package/src/runtime/routes/content-source-routes.ts +5 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +5 -1
- package/src/runtime/routes/conversation-attention-routes.ts +5 -0
- package/src/runtime/routes/conversation-cli-routes.ts +54 -7
- package/src/runtime/routes/conversation-compaction-routes.ts +54 -25
- package/src/runtime/routes/conversation-list-routes.ts +81 -12
- package/src/runtime/routes/conversation-management-routes.ts +57 -14
- package/src/runtime/routes/conversation-query-routes.ts +88 -41
- package/src/runtime/routes/conversation-routes.ts +74 -19
- package/src/runtime/routes/conversation-starter-routes.ts +22 -13
- package/src/runtime/routes/conversations-import-routes.ts +6 -1
- package/src/runtime/routes/credential-prompt-routes.ts +5 -0
- package/src/runtime/routes/credential-routes.ts +25 -6
- package/src/runtime/routes/debug-bash-routes.ts +5 -0
- package/src/runtime/routes/debug-routes.ts +11 -2
- package/src/runtime/routes/defer-routes.ts +13 -0
- package/src/runtime/routes/diagnostics-routes.ts +37 -46
- package/src/runtime/routes/disk-pressure-routes.ts +17 -31
- package/src/runtime/routes/document-comments-routes.ts +46 -27
- package/src/runtime/routes/documents-routes.ts +21 -10
- package/src/runtime/routes/domain-routes.ts +61 -28
- package/src/runtime/routes/email-routes.ts +33 -0
- package/src/runtime/routes/events-routes.ts +114 -9
- package/src/runtime/routes/filing-routes.ts +9 -4
- package/src/runtime/routes/gateway-log-routes.ts +5 -0
- package/src/runtime/routes/global-search-routes.ts +53 -50
- package/src/runtime/routes/group-routes.ts +21 -5
- package/src/runtime/routes/guardian-action-routes.ts +9 -0
- package/src/runtime/routes/guardian-approval-interception.ts +0 -31
- package/src/runtime/routes/heartbeat-routes.ts +25 -9
- package/src/runtime/routes/home-feed-routes.ts +23 -19
- package/src/runtime/routes/home-state-routes.ts +8 -40
- package/src/runtime/routes/host-app-control-routes.ts +5 -0
- package/src/runtime/routes/host-bash-routes.ts +5 -0
- package/src/runtime/routes/host-browser-routes.ts +13 -0
- package/src/runtime/routes/host-cu-routes.ts +5 -0
- package/src/runtime/routes/host-file-routes.ts +26 -6
- package/src/runtime/routes/host-transfer-routes.ts +13 -2
- package/src/runtime/routes/http-adapter.ts +1 -2
- package/src/runtime/routes/identity-intro-cache.ts +17 -6
- package/src/runtime/routes/identity-routes.ts +12 -2
- package/src/runtime/routes/image-generation-routes.ts +5 -0
- package/src/runtime/routes/inbound-message-handler.ts +15 -11
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +0 -12
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +15 -19
- package/src/runtime/routes/inference-profile-session-routes.ts +13 -3
- package/src/runtime/routes/inference-provider-connection-routes.ts +21 -5
- package/src/runtime/routes/inference-send-routes.ts +11 -11
- package/src/runtime/routes/integrations/a2a.ts +30 -7
- package/src/runtime/routes/integrations/slack/channel.ts +19 -3
- package/src/runtime/routes/integrations/slack/share.ts +9 -2
- package/src/runtime/routes/integrations/telegram.ts +28 -9
- package/src/runtime/routes/integrations/twilio.ts +35 -7
- package/src/runtime/routes/integrations/vercel.ts +3 -3
- package/src/runtime/routes/internal-oauth-routes.ts +5 -0
- package/src/runtime/routes/internal-twilio-routes.ts +13 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +39 -4
- package/src/runtime/routes/log-export-routes.ts +28 -10
- package/src/runtime/routes/mcp-auth-routes.ts +25 -0
- package/src/runtime/routes/memory-item-routes.ts +21 -10
- package/src/runtime/routes/memory-v2-routes.ts +90 -36
- package/src/runtime/routes/memory-v3-routes.ts +273 -407
- package/src/runtime/routes/migration-rollback-routes.ts +5 -1
- package/src/runtime/routes/migration-routes.ts +29 -0
- package/src/runtime/routes/notification-routes.ts +17 -1
- package/src/runtime/routes/oauth-apps.ts +33 -11
- package/src/runtime/routes/oauth-commands-routes.ts +37 -14
- package/src/runtime/routes/oauth-connect-routes.ts +9 -0
- package/src/runtime/routes/oauth-lifecycle-routes.ts +5 -1
- package/src/runtime/routes/oauth-providers.ts +35 -10
- package/src/runtime/routes/platform-routes.ts +21 -0
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +3 -2
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +37 -16
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +7 -3
- package/src/runtime/routes/playground/__tests__/state.test.ts +10 -3
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +0 -1
- package/src/runtime/routes/playground/inject-failures.ts +13 -8
- package/src/runtime/routes/playground/reset-circuit.ts +14 -9
- package/src/runtime/routes/playground/seed-conversation.ts +1 -1
- package/src/runtime/routes/playground/seeded-conversations.ts +3 -3
- package/src/runtime/routes/playground/state.ts +4 -3
- package/src/runtime/routes/plugins-routes.ts +22 -19
- package/src/runtime/routes/profiler-routes.ts +17 -4
- package/src/runtime/routes/ps-routes.ts +5 -0
- package/src/runtime/routes/publish-routes.ts +13 -3
- package/src/runtime/routes/question-routes.ts +5 -0
- package/src/runtime/routes/recording-routes.ts +25 -12
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/sanity-routes.ts +9 -2
- package/src/runtime/routes/schedule-routes.ts +137 -47
- package/src/runtime/routes/secret-routes.ts +17 -4
- package/src/runtime/routes/sequence-routes.ts +33 -0
- package/src/runtime/routes/settings-routes.ts +65 -19
- package/src/runtime/routes/skills-routes.ts +133 -69
- package/src/runtime/routes/slack-channel-routes.ts +5 -0
- package/src/runtime/routes/stt-routes.ts +13 -6
- package/src/runtime/routes/subagents-routes.ts +24 -18
- package/src/runtime/routes/suggest-trust-rule-routes.ts +7 -2
- package/src/runtime/routes/surface-action-routes.ts +9 -0
- package/src/runtime/routes/surface-content-routes.ts +10 -2
- package/src/runtime/routes/task-routes.ts +37 -0
- package/src/runtime/routes/telemetry-routes.ts +9 -0
- package/src/runtime/routes/trace-event-routes.ts +42 -1
- package/src/runtime/routes/trust-rules-routes.ts +5 -0
- package/src/runtime/routes/tts-routes.ts +13 -6
- package/src/runtime/routes/types.ts +17 -8
- package/src/runtime/routes/ui-request-routes.ts +5 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +5 -0
- package/src/runtime/routes/usage-routes.ts +71 -3
- package/src/runtime/routes/user-routes-cli.ts +9 -0
- package/src/runtime/routes/user-routes.ts +5 -1
- package/src/runtime/routes/wake-conversation-routes.ts +5 -0
- package/src/runtime/routes/watcher-routes.ts +21 -0
- package/src/runtime/routes/webhook-routes.ts +9 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +5 -0
- package/src/runtime/routes/work-items-routes.ts +47 -19
- package/src/runtime/routes/workspace-commit-routes.ts +5 -0
- package/src/runtime/routes/workspace-routes.test.ts +42 -0
- package/src/runtime/routes/workspace-routes.ts +120 -9
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -4
- package/src/runtime/services/analyze-conversation.ts +3 -6
- package/src/runtime/services/conversation-serializer.ts +24 -2
- package/src/runtime/sync/resource-sync-events.ts +16 -2
- package/src/runtime/sync/sync-publisher.ts +2 -2
- package/src/schedule/run-script.ts +28 -3
- package/src/schedule/schedule-store.ts +8 -0
- package/src/schedule/scheduler.ts +3 -1
- package/src/signals/user-message.ts +5 -8
- package/src/skills/catalog-files.ts +4 -1
- package/src/skills/clawhub-files.ts +2 -0
- package/src/skills/skillssh-files.ts +2 -0
- package/src/subagent/manager.ts +3 -6
- package/src/telemetry/types.ts +26 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +138 -1
- package/src/telemetry/usage-telemetry-reporter.ts +31 -0
- package/src/tools/acp/spawn.test.ts +88 -38
- package/src/tools/apps/definitions.ts +8 -4
- package/src/tools/ask-question/ask-question-tool.test.ts +120 -105
- package/src/tools/ask-question/ask-question-tool.ts +85 -90
- package/src/tools/computer-use/definitions.ts +28 -24
- package/src/tools/credential-execution/make-authenticated-request.ts +56 -51
- package/src/tools/credential-execution/manage-secure-command-tool.ts +2 -2
- package/src/tools/credential-execution/run-authenticated-command.ts +82 -77
- package/src/tools/credentials/vault.ts +112 -111
- package/src/tools/execution-target.ts +1 -1
- package/src/tools/execution-timeout.ts +3 -4
- package/src/tools/filesystem/edit.ts +45 -42
- package/src/tools/filesystem/list.ts +33 -30
- package/src/tools/filesystem/read.ts +54 -35
- package/src/tools/filesystem/write.ts +34 -31
- package/src/tools/host-filesystem/edit.ts +44 -42
- package/src/tools/host-filesystem/read.ts +49 -35
- package/src/tools/host-filesystem/transfer.ts +121 -108
- package/src/tools/host-filesystem/write.ts +33 -31
- package/src/tools/host-terminal/host-shell.ts +50 -48
- package/src/tools/memory/register.ts +23 -24
- package/src/tools/network/web-fetch.ts +49 -46
- package/src/tools/network/web-search.ts +16 -13
- package/src/tools/registry.ts +39 -16
- package/src/tools/schedule/create.ts +11 -0
- package/src/tools/schedule/update.ts +16 -0
- package/src/tools/shared/filesystem/audio-read.ts +122 -0
- package/src/tools/shared/filesystem/image-read.ts +1 -1
- package/src/tools/skills/execute.ts +34 -31
- package/src/tools/skills/load.ts +29 -23
- package/src/tools/subagent/notify-parent.ts +35 -32
- package/src/tools/system/avatar-generator.ts +13 -22
- package/src/tools/system/request-permission.ts +30 -27
- package/src/tools/terminal/shell.ts +190 -61
- package/src/tools/tool-defaults.ts +20 -9
- package/src/tools/tool-manifest.ts +4 -4
- package/src/tools/types.ts +74 -23
- package/src/tools/ui-surface/definitions.ts +69 -9
- package/src/usage/types.ts +10 -0
- package/src/util/errors.ts +2 -2
- package/src/util/map-limit.ts +27 -0
- package/src/util/platform.ts +15 -12
- package/src/work-items/work-item-runner.ts +7 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +7 -20
- package/src/workspace/migrations/092-backfill-v3-leaves.ts +169 -0
- package/src/workspace/migrations/093-backfill-leaf-ids.ts +144 -0
- package/src/workspace/migrations/094-seed-avatar-manifest.ts +155 -0
- package/src/workspace/migrations/__tests__/094-seed-avatar-manifest.test.ts +136 -0
- package/src/workspace/migrations/__tests__/backfill-leaf-ids.test.ts +175 -0
- package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +124 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/provider-commit-message-generator.ts +15 -17
- package/tsconfig.json +4 -1
- package/src/__tests__/history-repair-pipeline.test.ts +0 -396
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +0 -340
- package/src/cli/commands/memory-v3-render.ts +0 -491
- package/src/daemon/message-types/disk-pressure.ts +0 -9
- package/src/email/feature-gate.ts +0 -23
- package/src/memory/v3/__tests__/coactivation-store.test.ts +0 -422
- package/src/memory/v3/__tests__/consolidation-job.test.ts +0 -466
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +0 -270
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +0 -324
- package/src/memory/v3/__tests__/edges.test.ts +0 -706
- package/src/memory/v3/__tests__/filter.test.ts +0 -560
- package/src/memory/v3/__tests__/gate.test.ts +0 -637
- package/src/memory/v3/__tests__/index-composition.test.ts +0 -291
- package/src/memory/v3/__tests__/loop.test.ts +0 -775
- package/src/memory/v3/__tests__/retriever.test.ts +0 -226
- package/src/memory/v3/__tests__/scouts.test.ts +0 -489
- package/src/memory/v3/__tests__/shadow-diff.test.ts +0 -225
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +0 -398
- package/src/memory/v3/__tests__/system-prompts.test.ts +0 -154
- package/src/memory/v3/__tests__/traversal.test.ts +0 -508
- package/src/memory/v3/__tests__/tree-index.test.ts +0 -280
- package/src/memory/v3/__tests__/tree-store.test.ts +0 -529
- package/src/memory/v3/__tests__/tree-walk.test.ts +0 -784
- package/src/memory/v3/__tests__/validate.test.ts +0 -277
- package/src/memory/v3/auto-edges.ts +0 -223
- package/src/memory/v3/coactivation-store.ts +0 -124
- package/src/memory/v3/consolidation-job.ts +0 -323
- package/src/memory/v3/coretrieval-seed.ts +0 -240
- package/src/memory/v3/edge-learning-job.ts +0 -160
- package/src/memory/v3/edges.ts +0 -286
- package/src/memory/v3/filter.ts +0 -286
- package/src/memory/v3/gate.ts +0 -349
- package/src/memory/v3/index-composition.ts +0 -126
- package/src/memory/v3/llm-capture.ts +0 -46
- package/src/memory/v3/loop.ts +0 -430
- package/src/memory/v3/maintenance.ts +0 -144
- package/src/memory/v3/prompt-context.ts +0 -33
- package/src/memory/v3/prompts/consolidation.ts +0 -458
- package/src/memory/v3/prompts/system-prompts.ts +0 -196
- package/src/memory/v3/retriever.ts +0 -33
- package/src/memory/v3/scouts.ts +0 -431
- package/src/memory/v3/shadow-diff.ts +0 -287
- package/src/memory/v3/shadow-middleware.ts +0 -347
- package/src/memory/v3/traversal.ts +0 -211
- package/src/memory/v3/tree-index.ts +0 -237
- package/src/memory/v3/tree-store.ts +0 -394
- package/src/memory/v3/tree-walk.ts +0 -356
- package/src/memory/v3/validate.ts +0 -323
- package/src/plugins/defaults/circuit-breaker.ts +0 -141
- package/src/plugins/defaults/compaction.ts +0 -141
- package/src/plugins/defaults/empty-response.ts +0 -124
- package/src/plugins/defaults/history-repair.ts +0 -83
- package/src/plugins/defaults/persistence.ts +0 -146
- package/src/plugins/defaults/title-generate.ts +0 -90
- package/src/plugins/defaults/token-estimate.ts +0 -101
- package/src/plugins/defaults/tool-error.ts +0 -119
- package/src/plugins/defaults/tool-result-truncate.ts +0 -84
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +0 -35
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory v3 — bundled lane system prompts + override resolution.
|
|
3
|
-
*
|
|
4
|
-
* The three v3 retrieval-loop lanes that make an LLM judgment call each carry a
|
|
5
|
-
* system prompt:
|
|
6
|
-
*
|
|
7
|
-
* - the dense-hit filter (`filter.ts`),
|
|
8
|
-
* - the tree-walk descent driver (`tree-walk.ts`),
|
|
9
|
-
* - the selection gate (`gate.ts`).
|
|
10
|
-
*
|
|
11
|
-
* The bundled bodies live here (under `prompts/`) so they are reviewable on
|
|
12
|
-
* their own, mirroring the convention established by the v2 router prompt
|
|
13
|
-
* (`../../v2/prompts/router.ts`). Operators may override any of the three at
|
|
14
|
-
* runtime via `memory.v3.prompts.<lane>` so the prompts can be iterated without
|
|
15
|
-
* a rebuild/restart — the same fast-iteration affordance the v2 router prompt
|
|
16
|
-
* already has.
|
|
17
|
-
*
|
|
18
|
-
* Each lane's config entry carries two seams, resolved highest-precedence
|
|
19
|
-
* first by {@link resolveV3SystemPrompt}:
|
|
20
|
-
* 1. `override` — an inline prompt string (takes precedence over the path).
|
|
21
|
-
* 2. `path` — a file whose contents replace the bundled body. Absolute paths
|
|
22
|
-
* are used as-is, a leading `~/` expands to the home directory, otherwise
|
|
23
|
-
* the path resolves under the workspace root.
|
|
24
|
-
*
|
|
25
|
-
* Failure handling is intentionally permissive: a missing file, read error,
|
|
26
|
-
* oversized file, or empty/whitespace-only body all log a warning and fall back
|
|
27
|
-
* to the bundled prompt. Retrieval must never break because of a bad override.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
import { lstatSync, readFileSync } from "node:fs";
|
|
31
|
-
import { homedir } from "node:os";
|
|
32
|
-
import { isAbsolute, join } from "node:path";
|
|
33
|
-
|
|
34
|
-
import { getLogger } from "../../../util/logger.js";
|
|
35
|
-
|
|
36
|
-
const log = getLogger("memory-v3-prompts");
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Hard upper bound on an override (inline or file). The bundled prompts are
|
|
40
|
-
* well under 4 KiB; 1 MiB is generous for any reasonable hand-edit while still
|
|
41
|
-
* preventing pathological inputs from being slurped into memory on every lane
|
|
42
|
-
* call. Matches the v2 router prompt's guard.
|
|
43
|
-
*/
|
|
44
|
-
const MAX_PROMPT_BYTES = 1 * 1024 * 1024;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Bundled system prompt for the fast dense-hit filter (`filter.ts`). Keeps the
|
|
48
|
-
* meaningful embedding-similarity associations and drops spurious
|
|
49
|
-
* near-neighbors before the more expensive selection gate runs.
|
|
50
|
-
*/
|
|
51
|
-
export const FILTER_SYSTEM_PROMPT =
|
|
52
|
-
"You are a fast relevance filter for a memory-retrieval loop. You are given " +
|
|
53
|
-
"candidate concept pages surfaced by embedding similarity for the current " +
|
|
54
|
-
"turn. Keep the pages that are meaningful associations and drop the " +
|
|
55
|
-
"spurious near-neighbors. When in doubt, keep.";
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Bundled system prompt for the tree-walk descent driver (`tree-walk.ts`).
|
|
59
|
-
* Decides which child nodes of the current memory-tree node to descend into.
|
|
60
|
-
*/
|
|
61
|
-
export const DESCENT_SYSTEM_PROMPT =
|
|
62
|
-
"You are the descent driver for a hierarchical memory-retrieval walk. At each " +
|
|
63
|
-
"node you see its child index (one line per child sub-node or leaf page) and " +
|
|
64
|
-
"the current conversation turn. Choose which child *nodes* to descend into to " +
|
|
65
|
-
"find the pages that bear on the next reply. Leaf pages are collected " +
|
|
66
|
-
"automatically — you only decide which branches to explore deeper.";
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Bundled system prompt for the selection gate (`gate.ts`). Decides whether the
|
|
70
|
-
* accumulated candidate pages are sufficient to answer the next reply.
|
|
71
|
-
*/
|
|
72
|
-
export const GATE_SYSTEM_PROMPT =
|
|
73
|
-
"You are the final selection gate for a memory-retrieval loop. You are " +
|
|
74
|
-
"given the candidate concept pages gathered so far for the current turn. " +
|
|
75
|
-
"Decide whether they are sufficient to answer the next reply. Lean toward " +
|
|
76
|
-
"recall: keep a candidate whenever it plausibly bears on the turn rather " +
|
|
77
|
-
"than dropping it. When the turn asks for a list, for 'all of' something, " +
|
|
78
|
-
"or for a broad answer, select every candidate that plausibly belongs — " +
|
|
79
|
-
"do not trim to only the most prominent ones. Drop a candidate only when it " +
|
|
80
|
-
"is clearly irrelevant to the turn.";
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* One lane's prompt-override config: an optional inline `override` string and
|
|
84
|
-
* an optional file `path`. Both default to `null`. Mirrors the v2 router
|
|
85
|
-
* prompt's `router_prompt_path` (file) plus inline-override seam, generalized
|
|
86
|
-
* to the three v3 lanes.
|
|
87
|
-
*/
|
|
88
|
-
export interface V3PromptOverrideConfig {
|
|
89
|
-
override: string | null;
|
|
90
|
-
path: string | null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Resolve a v3 lane's system prompt, applying the configured override (inline
|
|
95
|
-
* first, then file path) and falling back to `bundled` when neither produces a
|
|
96
|
-
* usable body. Unlike the v2 router prompt these bodies carry no placeholders,
|
|
97
|
-
* so the resolved contents are returned verbatim.
|
|
98
|
-
*
|
|
99
|
-
* @param bundled The lane's bundled default body (the fallback).
|
|
100
|
-
* @param config The lane's `memory.v3.prompts.<lane>` config, if present.
|
|
101
|
-
* @param workspaceDir Workspace root used to resolve a relative file `path`.
|
|
102
|
-
*/
|
|
103
|
-
export function resolveV3SystemPrompt(
|
|
104
|
-
bundled: string,
|
|
105
|
-
config: V3PromptOverrideConfig | undefined,
|
|
106
|
-
workspaceDir: string,
|
|
107
|
-
): string {
|
|
108
|
-
// Inline override wins over the file path and the bundled body. An
|
|
109
|
-
// empty/whitespace-only string is treated as "no override" so a cleared
|
|
110
|
-
// config value falls through to the path/bundled resolution.
|
|
111
|
-
const inline = config?.override ?? null;
|
|
112
|
-
if (inline !== null) {
|
|
113
|
-
if (inline.length > MAX_PROMPT_BYTES) {
|
|
114
|
-
log.warn(
|
|
115
|
-
{
|
|
116
|
-
size: inline.length,
|
|
117
|
-
limit: MAX_PROMPT_BYTES,
|
|
118
|
-
reason: "oversized_inline_override",
|
|
119
|
-
fallback: "path_or_bundled",
|
|
120
|
-
},
|
|
121
|
-
"v3 system-prompt inline override exceeds size limit; falling back",
|
|
122
|
-
);
|
|
123
|
-
} else if (inline.trim().length > 0) {
|
|
124
|
-
return inline;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const path = config?.path ?? null;
|
|
129
|
-
if (path === null) return bundled;
|
|
130
|
-
|
|
131
|
-
const resolvedPath = resolveOverridePath(path, workspaceDir);
|
|
132
|
-
let contents: string;
|
|
133
|
-
try {
|
|
134
|
-
const stat = lstatSync(resolvedPath);
|
|
135
|
-
if (!stat.isFile()) {
|
|
136
|
-
log.warn(
|
|
137
|
-
{
|
|
138
|
-
configuredPath: path,
|
|
139
|
-
resolvedPath,
|
|
140
|
-
reason: "not_regular_file",
|
|
141
|
-
fallback: "bundled",
|
|
142
|
-
},
|
|
143
|
-
"v3 system-prompt override is not a regular file; using bundled prompt",
|
|
144
|
-
);
|
|
145
|
-
return bundled;
|
|
146
|
-
}
|
|
147
|
-
if (stat.size > MAX_PROMPT_BYTES) {
|
|
148
|
-
log.warn(
|
|
149
|
-
{
|
|
150
|
-
configuredPath: path,
|
|
151
|
-
resolvedPath,
|
|
152
|
-
size: stat.size,
|
|
153
|
-
limit: MAX_PROMPT_BYTES,
|
|
154
|
-
reason: "oversized_override",
|
|
155
|
-
fallback: "bundled",
|
|
156
|
-
},
|
|
157
|
-
"v3 system-prompt override exceeds size limit; using bundled prompt",
|
|
158
|
-
);
|
|
159
|
-
return bundled;
|
|
160
|
-
}
|
|
161
|
-
contents = readFileSync(resolvedPath, "utf-8");
|
|
162
|
-
} catch (err) {
|
|
163
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
164
|
-
log.warn(
|
|
165
|
-
{ configuredPath: path, resolvedPath, code, fallback: "bundled" },
|
|
166
|
-
"v3 system-prompt override unreadable; using bundled prompt",
|
|
167
|
-
);
|
|
168
|
-
return bundled;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (contents.trim().length === 0) {
|
|
172
|
-
log.warn(
|
|
173
|
-
{
|
|
174
|
-
configuredPath: path,
|
|
175
|
-
resolvedPath,
|
|
176
|
-
reason: "empty_override",
|
|
177
|
-
fallback: "bundled",
|
|
178
|
-
},
|
|
179
|
-
"v3 system-prompt override is empty; using bundled prompt",
|
|
180
|
-
);
|
|
181
|
-
return bundled;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return contents;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function resolveOverridePath(
|
|
188
|
-
overridePath: string,
|
|
189
|
-
workspaceDir: string,
|
|
190
|
-
): string {
|
|
191
|
-
if (overridePath.startsWith("~/")) {
|
|
192
|
-
return join(homedir(), overridePath.slice(2));
|
|
193
|
-
}
|
|
194
|
-
if (isAbsolute(overridePath)) return overridePath;
|
|
195
|
-
return join(workspaceDir, overridePath);
|
|
196
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v3 retriever — the multi-lane bounded-descent retrieval loop
|
|
3
|
-
* ({@link runRetrievalLoop}) adapted to the harness {@link Retriever}
|
|
4
|
-
* interface.
|
|
5
|
-
*
|
|
6
|
-
* This is the offline, zero-production-risk shadow path: the comparison harness
|
|
7
|
-
* replays historical oracle turns and scores v3's selection against the v2
|
|
8
|
-
* router's logged picks (recall@k). Nothing here runs on a live injection turn
|
|
9
|
-
* — the loop reads the DB handle for its hot lane but never mutates production
|
|
10
|
-
* state, matching the {@link Retriever} contract.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { DrizzleDb } from "../db-connection.js";
|
|
14
|
-
import type {
|
|
15
|
-
RetrievalInput,
|
|
16
|
-
RetrievalOutput,
|
|
17
|
-
Retriever,
|
|
18
|
-
} from "../v2/harness/retriever.js";
|
|
19
|
-
import { runRetrievalLoop } from "./loop.js";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Wrap the v3 retrieval loop as a named harness {@link Retriever}.
|
|
23
|
-
*
|
|
24
|
-
* @param db handle threaded to {@link runRetrievalLoop} for the scout hot lane.
|
|
25
|
-
*/
|
|
26
|
-
export function createV3Retriever(db: DrizzleDb): Retriever {
|
|
27
|
-
return {
|
|
28
|
-
name: "v3",
|
|
29
|
-
retrieve(input: RetrievalInput): Promise<RetrievalOutput> {
|
|
30
|
-
return runRetrievalLoop(input, { db });
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
package/src/memory/v3/scouts.ts
DELETED
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Memory v3 — Always-on scout lanes (hot / sparse / dense)
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
//
|
|
5
|
-
// The v3 retrieval loop opens each pass by fanning out a small set of cheap,
|
|
6
|
-
// always-on "scout" lanes over the v2 read-substrate. Scouts surface candidate
|
|
7
|
-
// concept-page slugs from three complementary signals before any LLM judging
|
|
8
|
-
// (the dense judge lives in a later PR) or tree descent runs:
|
|
9
|
-
//
|
|
10
|
-
// - hot: corpus-global access-frequency EMA via `computeInjectionScores`.
|
|
11
|
-
// Retriever-agnostic — v2 keeps writing `memory_v2_injection_events`,
|
|
12
|
-
// so a page the user has been touching is "hot" regardless of which
|
|
13
|
-
// retriever surfaced it. Hits are seeded as ordinary **candidates**
|
|
14
|
-
// (not sticky) so the query-aware downstream gate can still drop a
|
|
15
|
-
// recency page that doesn't bear on the turn.
|
|
16
|
-
// - sparse: BM25 keyword match. Near-exact (high-score) hits are both
|
|
17
|
-
// **sticky** and **tree-bypass** — a literal keyword hit is a strong
|
|
18
|
-
// enough signal that we shouldn't make the slug earn its place by
|
|
19
|
-
// walking the tree.
|
|
20
|
-
// - dense: embedding-similarity match, then an asymmetric per-subtree quota
|
|
21
|
-
// (generous active-domain slice, thin off-domain slice) plus MMR for
|
|
22
|
-
// diversity so a single dominant subtree can't crowd out the slate.
|
|
23
|
-
//
|
|
24
|
-
// Each lane is individually toggleable via `config.memory.v3.lanes`. This module
|
|
25
|
-
// performs **no** LLM calls and writes nothing — it is a pure read over the v2
|
|
26
|
-
// substrate. A later PR composes `runScouts` into the full descent loop.
|
|
27
|
-
|
|
28
|
-
import type { AssistantConfig } from "../../config/types.js";
|
|
29
|
-
import { applyCorrectionIfCalibrated } from "../anisotropy.js";
|
|
30
|
-
import type { DrizzleDb } from "../db-connection.js";
|
|
31
|
-
import { embedWithBackend } from "../embedding-backend.js";
|
|
32
|
-
import type { RetrievalInput } from "../v2/harness/retriever.js";
|
|
33
|
-
import type { ScoutResult } from "../v2/harness/trace.js";
|
|
34
|
-
import { computeInjectionScores } from "../v2/injection-events.js";
|
|
35
|
-
import { getPageIndex } from "../v2/page-index.js";
|
|
36
|
-
import { hybridQueryConceptPages } from "../v2/qdrant.js";
|
|
37
|
-
import { generateBm25QueryEmbedding } from "../v2/sparse-bm25.js";
|
|
38
|
-
|
|
39
|
-
/** Result of running the always-on scout fanout for one pass. */
|
|
40
|
-
export interface RunScoutsResult {
|
|
41
|
-
/** Per-lane contributions, one entry per *enabled* lane that produced hits. */
|
|
42
|
-
scouts: ScoutResult[];
|
|
43
|
-
/**
|
|
44
|
-
* Slugs the downstream gate should keep in the running regardless of later
|
|
45
|
-
* scoring — near-exact sparse hits. Hot-lane hits are deliberately excluded:
|
|
46
|
-
* they contribute candidates but must earn their place through the gate.
|
|
47
|
-
*/
|
|
48
|
-
sticky: Set<string>;
|
|
49
|
-
/**
|
|
50
|
-
* Slugs strong enough (near-exact sparse) to skip the tree-descent gate
|
|
51
|
-
* entirely. A subset of `sticky`.
|
|
52
|
-
*/
|
|
53
|
-
bypass: Set<string>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Substrate dependencies injected for testability. */
|
|
57
|
-
export interface ScoutDeps {
|
|
58
|
-
db: DrizzleDb;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// Tunables
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Per-lane hit cap before quota/diversity post-processing. The lanes are
|
|
67
|
-
* always-on and run every pass, so a generous-but-bounded cap keeps the dense
|
|
68
|
-
* Qdrant round-trip and the per-lane bookkeeping cheap while still giving the
|
|
69
|
-
* quota/MMR step enough raw candidates to choose from.
|
|
70
|
-
*/
|
|
71
|
-
const LANE_QUERY_LIMIT = 100;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Sparse score at or above which a hit is treated as **near-exact** — sticky
|
|
75
|
-
* and tree-bypass. BM25 scores are unbounded above and corpus-relative, so the
|
|
76
|
-
* threshold is taken relative to the top sparse hit in the same pass rather
|
|
77
|
-
* than as a fixed magnitude: a hit within this fraction of the best sparse
|
|
78
|
-
* score for the query is "near-exact". A lone strong hit (it is its own max)
|
|
79
|
-
* always qualifies.
|
|
80
|
-
*/
|
|
81
|
-
const SPARSE_NEAR_EXACT_FRACTION = 0.9;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* MMR trade-off: `λ · relevance − (1 − λ) · redundancy`. Closer to 1 favors
|
|
85
|
-
* raw dense relevance; lower values push harder for subtree diversity. 0.7
|
|
86
|
-
* keeps relevance in the driver's seat while still breaking up runs of
|
|
87
|
-
* same-subtree hits.
|
|
88
|
-
*/
|
|
89
|
-
const DENSE_MMR_LAMBDA = 0.7;
|
|
90
|
-
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// Public entry point
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Run the always-on scout lanes for one retrieval pass.
|
|
97
|
-
*
|
|
98
|
-
* The dense lane embeds `queryText` (the last user turn joined with
|
|
99
|
-
* `input.nowText`, the same shape the v2 router/activation path embeds). The
|
|
100
|
-
* sparse lane keys off `userText` (the user turn alone) so NOW context can't
|
|
101
|
-
* make whatever it mentions a near-exact sticky hit on every turn. Disabled
|
|
102
|
-
* lanes (per `config.memory.v3.lanes`) are skipped entirely: no substrate call,
|
|
103
|
-
* no `ScoutResult` entry.
|
|
104
|
-
*
|
|
105
|
-
* Honors `input.signal` — aborts between lanes and around the dense embed.
|
|
106
|
-
*/
|
|
107
|
-
export async function runScouts(
|
|
108
|
-
input: RetrievalInput,
|
|
109
|
-
deps: ScoutDeps,
|
|
110
|
-
): Promise<RunScoutsResult> {
|
|
111
|
-
const { config, signal } = input;
|
|
112
|
-
const lanes = config.memory.v3.lanes;
|
|
113
|
-
const queryText = deriveQueryText(input);
|
|
114
|
-
const userText = deriveUserText(input);
|
|
115
|
-
|
|
116
|
-
const scouts: ScoutResult[] = [];
|
|
117
|
-
const sticky = new Set<string>();
|
|
118
|
-
const bypass = new Set<string>();
|
|
119
|
-
|
|
120
|
-
// Hot lane — corpus-global EMA over the full slug universe. Cheap (single
|
|
121
|
-
// SQL pass) so it runs first. Hot hits are seeded as ordinary candidates but
|
|
122
|
-
// NOT sticky: the EMA ranks recency/frequency, not query relevance, so
|
|
123
|
-
// force-keeping the top-N recency pages would dominate every turn with
|
|
124
|
-
// operationally-frequent pages instead of the pages that bear on the query.
|
|
125
|
-
// Letting them pass through the query-aware gate lets irrelevant ones drop.
|
|
126
|
-
if (lanes.hot) {
|
|
127
|
-
signal?.throwIfAborted();
|
|
128
|
-
const hot = await runHotLane(input, deps);
|
|
129
|
-
if (hot) scouts.push(hot);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Sparse lane — BM25 keyword match on the user's words ONLY (not the NOW
|
|
133
|
-
// context). NOW is ambient standing text; folding it into a keyword query
|
|
134
|
-
// makes whatever pages NOW happens to mention score near-exact on every
|
|
135
|
-
// turn, and near-exact hits become sticky + tree-bypass — so NOW-referenced
|
|
136
|
-
// pages would be force-injected into every selection regardless of the
|
|
137
|
-
// query. Keying sparse off the user turn keeps lexical match, sticky, and
|
|
138
|
-
// bypass tied to what the user actually asked. (Dense still embeds NOW below;
|
|
139
|
-
// semantic context legitimately helps there.)
|
|
140
|
-
if (lanes.sparse && userText.length > 0) {
|
|
141
|
-
signal?.throwIfAborted();
|
|
142
|
-
const sparse = await runSparseLane(userText, signal);
|
|
143
|
-
if (sparse) {
|
|
144
|
-
scouts.push(sparse.result);
|
|
145
|
-
for (const slug of sparse.nearExact) {
|
|
146
|
-
sticky.add(slug);
|
|
147
|
-
bypass.add(slug);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Dense lane — embedding similarity, then per-subtree quota + MMR.
|
|
153
|
-
if (lanes.dense && queryText.length > 0) {
|
|
154
|
-
signal?.throwIfAborted();
|
|
155
|
-
const dense = await runDenseLane(queryText, config, signal);
|
|
156
|
-
if (dense) scouts.push(dense);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return { scouts, sticky, bypass };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
// Query-text derivation
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* The just-arrived user turn's text — the last `recentTurnPairs` entry's
|
|
168
|
-
* `userMessage`. This is the keyword target for the sparse lane and the basis
|
|
169
|
-
* for near-exact sticky/bypass, which must reflect what the user actually
|
|
170
|
-
* asked rather than the ambient NOW context.
|
|
171
|
-
*/
|
|
172
|
-
function deriveUserText(input: RetrievalInput): string {
|
|
173
|
-
const lastPair = input.recentTurnPairs[input.recentTurnPairs.length - 1];
|
|
174
|
-
return (lastPair?.userMessage ?? "").trim();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Build the dense-lane query text from the just-arrived user turn plus the NOW
|
|
179
|
-
* context. Mirrors the v2 activation path (`selectCandidates`): join the
|
|
180
|
-
* non-empty channels with a newline. NOW is included here because semantic
|
|
181
|
-
* embedding benefits from standing context; the sparse lane deliberately omits
|
|
182
|
-
* it (see {@link deriveUserText}).
|
|
183
|
-
*/
|
|
184
|
-
function deriveQueryText(input: RetrievalInput): string {
|
|
185
|
-
return [deriveUserText(input), input.nowText]
|
|
186
|
-
.filter((s) => s.trim().length > 0)
|
|
187
|
-
.join("\n")
|
|
188
|
-
.trim();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
// Hot lane
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
|
|
195
|
-
async function runHotLane(
|
|
196
|
-
input: RetrievalInput,
|
|
197
|
-
deps: ScoutDeps,
|
|
198
|
-
): Promise<ScoutResult | null> {
|
|
199
|
-
const index = await getPageIndex(input.workspaceDir);
|
|
200
|
-
const allSlugs = index.entries.map((e) => e.slug);
|
|
201
|
-
if (allSlugs.length === 0) return null;
|
|
202
|
-
|
|
203
|
-
const now = Date.now();
|
|
204
|
-
const scores = computeInjectionScores(deps.db, allSlugs, now);
|
|
205
|
-
if (scores.size === 0) return null;
|
|
206
|
-
|
|
207
|
-
// Slugs with no events in the read window are omitted by
|
|
208
|
-
// `computeInjectionScores`, so every entry here has score > 0. Cap to the
|
|
209
|
-
// top `hotLimit` by EMA: on a mature corpus — where nearly every page has
|
|
210
|
-
// been injected at some point — an uncapped lane would flood the candidate
|
|
211
|
-
// set with the entire corpus, so keep only the strongest recency signals.
|
|
212
|
-
const ranked = [...scores.entries()]
|
|
213
|
-
.sort((a, b) => sortByScoreDesc(a, b))
|
|
214
|
-
.slice(0, input.config.memory.v3.hotLimit);
|
|
215
|
-
const slugs = ranked.map(([slug]) => slug);
|
|
216
|
-
const scoreBySlug = Object.fromEntries(ranked);
|
|
217
|
-
return { lane: "hot", slugs, scoreBySlug };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
// Sparse lane
|
|
222
|
-
// ---------------------------------------------------------------------------
|
|
223
|
-
|
|
224
|
-
async function runSparseLane(
|
|
225
|
-
queryText: string,
|
|
226
|
-
signal: AbortSignal | undefined,
|
|
227
|
-
): Promise<{ result: ScoutResult; nearExact: string[] } | null> {
|
|
228
|
-
const sparse = generateBm25QueryEmbedding(queryText);
|
|
229
|
-
if (sparse.indices.length === 0) return null;
|
|
230
|
-
|
|
231
|
-
// Dense channel intentionally empty — this lane is BM25-only. `skipSparse:
|
|
232
|
-
// false` keeps the sparse round-trip on; we read `sparseScore` and ignore
|
|
233
|
-
// any dense scores the query happens to surface.
|
|
234
|
-
const hits = await hybridQueryConceptPages(
|
|
235
|
-
[],
|
|
236
|
-
sparse,
|
|
237
|
-
LANE_QUERY_LIMIT,
|
|
238
|
-
undefined,
|
|
239
|
-
{
|
|
240
|
-
skipSparse: false,
|
|
241
|
-
},
|
|
242
|
-
);
|
|
243
|
-
signal?.throwIfAborted();
|
|
244
|
-
|
|
245
|
-
const scored = hits
|
|
246
|
-
.map((hit) => ({ slug: hit.slug, score: hit.sparseScore }))
|
|
247
|
-
.filter((h): h is { slug: string; score: number } => h.score !== undefined)
|
|
248
|
-
.sort((a, b) => b.score - a.score);
|
|
249
|
-
if (scored.length === 0) return null;
|
|
250
|
-
|
|
251
|
-
const slugs = scored.map((h) => h.slug);
|
|
252
|
-
const scoreBySlug = Object.fromEntries(scored.map((h) => [h.slug, h.score]));
|
|
253
|
-
|
|
254
|
-
// Near-exact: within SPARSE_NEAR_EXACT_FRACTION of the top sparse score.
|
|
255
|
-
const topScore = scored[0].score;
|
|
256
|
-
const threshold = topScore * SPARSE_NEAR_EXACT_FRACTION;
|
|
257
|
-
const nearExact = scored
|
|
258
|
-
.filter((h) => topScore > 0 && h.score >= threshold)
|
|
259
|
-
.map((h) => h.slug);
|
|
260
|
-
|
|
261
|
-
return { result: { lane: "sparse", slugs, scoreBySlug }, nearExact };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// ---------------------------------------------------------------------------
|
|
265
|
-
// Dense lane
|
|
266
|
-
// ---------------------------------------------------------------------------
|
|
267
|
-
|
|
268
|
-
async function runDenseLane(
|
|
269
|
-
queryText: string,
|
|
270
|
-
config: AssistantConfig,
|
|
271
|
-
signal: AbortSignal | undefined,
|
|
272
|
-
): Promise<ScoutResult | null> {
|
|
273
|
-
// Embed + apply anisotropy correction, mirroring v2 activation's read path.
|
|
274
|
-
const embedded = await embedWithBackend(config, [queryText], { signal });
|
|
275
|
-
const dense = await applyCorrectionIfCalibrated(
|
|
276
|
-
embedded.vectors[0],
|
|
277
|
-
embedded.provider,
|
|
278
|
-
embedded.model,
|
|
279
|
-
);
|
|
280
|
-
signal?.throwIfAborted();
|
|
281
|
-
|
|
282
|
-
const sparse = generateBm25QueryEmbedding(queryText);
|
|
283
|
-
const hits = await hybridQueryConceptPages(dense, sparse, LANE_QUERY_LIMIT);
|
|
284
|
-
signal?.throwIfAborted();
|
|
285
|
-
|
|
286
|
-
const scored = hits
|
|
287
|
-
.map((hit) => ({ slug: hit.slug, score: hit.denseScore }))
|
|
288
|
-
.filter((h): h is { slug: string; score: number } => h.score !== undefined)
|
|
289
|
-
.sort((a, b) => b.score - a.score);
|
|
290
|
-
if (scored.length === 0) return null;
|
|
291
|
-
|
|
292
|
-
const selected = applyQuotaAndMmr(scored, config.memory.v3);
|
|
293
|
-
if (selected.length === 0) return null;
|
|
294
|
-
|
|
295
|
-
const slugs = selected.map((h) => h.slug);
|
|
296
|
-
const scoreBySlug = Object.fromEntries(
|
|
297
|
-
selected.map((h) => [h.slug, h.score]),
|
|
298
|
-
);
|
|
299
|
-
return { lane: "dense", slugs, scoreBySlug };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
interface ScoredSlug {
|
|
303
|
-
slug: string;
|
|
304
|
-
score: number;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Apply the asymmetric per-subtree quota then MMR re-ranking to the dense hits.
|
|
309
|
-
*
|
|
310
|
-
* Quota: the conversation's **active domain** is the top-path segment of the
|
|
311
|
-
* single highest-scoring dense hit. That domain gets a generous slice
|
|
312
|
-
* (`denseQuota.activeDomain`); every other (off-)domain shares a thin slice
|
|
313
|
-
* (`denseQuota.offDomain`) so exploratory hits aren't fully starved but can't
|
|
314
|
-
* dominate either. Quotas are per-domain caps applied in score-descending
|
|
315
|
-
* order.
|
|
316
|
-
*
|
|
317
|
-
* MMR: re-rank the quota-passing pool by `λ · relevance − (1 − λ) · redundancy`
|
|
318
|
-
* where redundancy is how represented the candidate's subtree already is in the
|
|
319
|
-
* selected slate. Without per-page embeddings we use subtree co-membership as
|
|
320
|
-
* the diversity signal — same subtree ⇒ maximally redundant. This breaks up
|
|
321
|
-
* runs of same-subtree hits without an extra Qdrant round-trip.
|
|
322
|
-
*/
|
|
323
|
-
function applyQuotaAndMmr(
|
|
324
|
-
scored: readonly ScoredSlug[],
|
|
325
|
-
v3: AssistantConfig["memory"]["v3"],
|
|
326
|
-
): ScoredSlug[] {
|
|
327
|
-
if (scored.length === 0) return [];
|
|
328
|
-
|
|
329
|
-
const activeDomain = domainOf(scored[0].slug);
|
|
330
|
-
const { activeDomain: activeQuota, offDomain: offQuota } = v3.denseQuota;
|
|
331
|
-
|
|
332
|
-
// Per-subtree quota: active domain gets activeQuota slots; all off-domain
|
|
333
|
-
// hits compete for a shared offQuota pool. Walk in score-desc order so the
|
|
334
|
-
// strongest hits claim each quota first.
|
|
335
|
-
const perDomainCount = new Map<string, number>();
|
|
336
|
-
let offDomainCount = 0;
|
|
337
|
-
const quotaPassing: ScoredSlug[] = [];
|
|
338
|
-
for (const hit of scored) {
|
|
339
|
-
const domain = domainOf(hit.slug);
|
|
340
|
-
if (domain === activeDomain) {
|
|
341
|
-
const used = perDomainCount.get(domain) ?? 0;
|
|
342
|
-
if (used >= activeQuota) continue;
|
|
343
|
-
perDomainCount.set(domain, used + 1);
|
|
344
|
-
} else {
|
|
345
|
-
if (offDomainCount >= offQuota) continue;
|
|
346
|
-
offDomainCount += 1;
|
|
347
|
-
}
|
|
348
|
-
quotaPassing.push(hit);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return mmrReorder(quotaPassing, DENSE_MMR_LAMBDA);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Greedy MMR over a score-ranked pool using subtree co-membership as the
|
|
356
|
-
* redundancy signal. Each pick maximizes
|
|
357
|
-
* `λ · normalizedScore − (1 − λ) · subtreeShareInSelected`, so once a subtree
|
|
358
|
-
* is well-represented its remaining members are deprioritized in favor of
|
|
359
|
-
* fresh subtrees of comparable relevance. Pure / deterministic.
|
|
360
|
-
*/
|
|
361
|
-
function mmrReorder(pool: readonly ScoredSlug[], lambda: number): ScoredSlug[] {
|
|
362
|
-
if (pool.length <= 1) return [...pool];
|
|
363
|
-
|
|
364
|
-
// Normalize relevance to [0, 1] so it shares a scale with the redundancy term
|
|
365
|
-
// (also [0, 1]). The pool is score-descending, so `pool[0]` is the max and the
|
|
366
|
-
// last entry is the min. When the max is positive we divide by it — the
|
|
367
|
-
// healthy case, kept exactly as-is. When every cosine is <= 0 (all candidates
|
|
368
|
-
// weakly/negatively similar) dividing by the max would collapse relevance to a
|
|
369
|
-
// single value and degrade ranking to diversity-only; instead normalize from
|
|
370
|
-
// the observed min..max range so the relevance ordering is preserved. A
|
|
371
|
-
// zero-width range (all scores equal) has no gradient to preserve, so it
|
|
372
|
-
// collapses to pure diversity.
|
|
373
|
-
const maxScore = pool[0].score;
|
|
374
|
-
const minScore = pool[pool.length - 1].score;
|
|
375
|
-
const range = maxScore - minScore;
|
|
376
|
-
const relevance = (hit: ScoredSlug): number => {
|
|
377
|
-
if (maxScore > 0) return hit.score / maxScore;
|
|
378
|
-
return range > 0 ? (hit.score - minScore) / range : 0;
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const remaining = [...pool];
|
|
382
|
-
const selected: ScoredSlug[] = [];
|
|
383
|
-
const selectedDomainCount = new Map<string, number>();
|
|
384
|
-
|
|
385
|
-
while (remaining.length > 0) {
|
|
386
|
-
let bestIdx = 0;
|
|
387
|
-
let bestMmr = -Infinity;
|
|
388
|
-
for (let i = 0; i < remaining.length; i++) {
|
|
389
|
-
const hit = remaining[i];
|
|
390
|
-
const domain = domainOf(hit.slug);
|
|
391
|
-
const share =
|
|
392
|
-
selected.length === 0
|
|
393
|
-
? 0
|
|
394
|
-
: (selectedDomainCount.get(domain) ?? 0) / selected.length;
|
|
395
|
-
const mmr = lambda * relevance(hit) - (1 - lambda) * share;
|
|
396
|
-
if (mmr > bestMmr) {
|
|
397
|
-
bestMmr = mmr;
|
|
398
|
-
bestIdx = i;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
const [pick] = remaining.splice(bestIdx, 1);
|
|
402
|
-
selected.push(pick);
|
|
403
|
-
const domain = domainOf(pick.slug);
|
|
404
|
-
selectedDomainCount.set(domain, (selectedDomainCount.get(domain) ?? 0) + 1);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return selected;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// ---------------------------------------------------------------------------
|
|
411
|
-
// Helpers
|
|
412
|
-
// ---------------------------------------------------------------------------
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* The "domain" (subtree) of a page slug — its top path segment. Slugs are
|
|
416
|
-
* path-relative with `/` separators (e.g. `people/alice` → `people`); a flat
|
|
417
|
-
* slug (`essentials`) is its own domain.
|
|
418
|
-
*/
|
|
419
|
-
function domainOf(slug: string): string {
|
|
420
|
-
const slash = slug.indexOf("/");
|
|
421
|
-
return slash === -1 ? slug : slug.slice(0, slash);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/** Score-desc with a stable slug-ASCII tiebreak. */
|
|
425
|
-
function sortByScoreDesc(
|
|
426
|
-
a: readonly [string, number],
|
|
427
|
-
b: readonly [string, number],
|
|
428
|
-
): number {
|
|
429
|
-
if (b[1] !== a[1]) return b[1] - a[1];
|
|
430
|
-
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
|
|
431
|
-
}
|