@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,706 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for `assistant/src/memory/v3/edges.ts` — the curated edge-expansion
|
|
3
|
-
* lane.
|
|
4
|
-
*
|
|
5
|
-
* Coverage matrix:
|
|
6
|
-
* - 1-hop and 2-hop outgoing expansion from a single seed.
|
|
7
|
-
* - Default hops (2) when omitted.
|
|
8
|
-
* - Seed excluded from its own `pulled`, and from another seed's `pulled`
|
|
9
|
-
* when one seed is reachable from another (seeds-excluded contract).
|
|
10
|
-
* - Multiple seeds: top-level `pulled` is the union; per-seed expansions
|
|
11
|
-
* attribute correctly; duplicate seeds collapse.
|
|
12
|
-
* - Per-seed cap spends slots on unique neighbors, not duplicates an earlier
|
|
13
|
-
* seed already pulled (recall at the cap).
|
|
14
|
-
* - `extraAdjacency` merges with the curated graph during traversal.
|
|
15
|
-
* - `extraAdjacency` bridges across hops (curated → extra → curated).
|
|
16
|
-
* - Cycles in the curated graph (and via extraAdjacency) terminate, bounded
|
|
17
|
-
* by hops + the visited set.
|
|
18
|
-
* - Empty seeds / orphan seed → empty result.
|
|
19
|
-
* - Provider-free: the only I/O is reading fixture concept pages.
|
|
20
|
-
*
|
|
21
|
-
* Tests live in temp workspaces (mkdtemp) and never touch `~/.vellum/`.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
25
|
-
import { tmpdir } from "node:os";
|
|
26
|
-
import { join } from "node:path";
|
|
27
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
28
|
-
|
|
29
|
-
import { invalidateEdgeIndex } from "../../v2/edge-index.js";
|
|
30
|
-
import { writePage } from "../../v2/page-store.js";
|
|
31
|
-
import type { ConceptPage } from "../../v2/types.js";
|
|
32
|
-
import { expandEdges } from "../edges.js";
|
|
33
|
-
|
|
34
|
-
let workspaceDir: string;
|
|
35
|
-
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-memory-v3-edges-"));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
// The v2 edge index caches module-locally; clear it so the next test's fresh
|
|
42
|
-
// workspace doesn't read a stale snapshot.
|
|
43
|
-
invalidateEdgeIndex();
|
|
44
|
-
if (existsSync(workspaceDir)) {
|
|
45
|
-
rmSync(workspaceDir, { recursive: true, force: true });
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
function makePage(slug: string, edges: string[] = []): ConceptPage {
|
|
50
|
-
return {
|
|
51
|
-
slug,
|
|
52
|
-
frontmatter: { edges, ref_files: [], ref_urls: [] },
|
|
53
|
-
body: "",
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Write a small chain/graph of pages by `{ slug: edges }` map. */
|
|
58
|
-
async function writeGraph(graph: Record<string, string[]>): Promise<void> {
|
|
59
|
-
for (const [slug, edges] of Object.entries(graph)) {
|
|
60
|
-
await writePage(workspaceDir, makePage(slug, edges));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Single-seed expansion
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
describe("expandEdges — single seed", () => {
|
|
69
|
-
test("1-hop expansion pulls only direct out-neighbors", async () => {
|
|
70
|
-
// alice -> bob -> carol
|
|
71
|
-
await writeGraph({ alice: ["bob"], bob: ["carol"], carol: [] });
|
|
72
|
-
|
|
73
|
-
const { pulled, expansions } = await expandEdges({
|
|
74
|
-
workspaceDir,
|
|
75
|
-
seeds: ["alice"],
|
|
76
|
-
hops: 1,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
expect([...pulled].sort()).toEqual(["bob"]);
|
|
80
|
-
expect(expansions).toEqual([{ from: "alice", pulled: ["bob"] }]);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("2-hop expansion pulls the 2-hop frontier", async () => {
|
|
84
|
-
// alice -> bob -> carol
|
|
85
|
-
await writeGraph({ alice: ["bob"], bob: ["carol"], carol: [] });
|
|
86
|
-
|
|
87
|
-
const { pulled, expansions } = await expandEdges({
|
|
88
|
-
workspaceDir,
|
|
89
|
-
seeds: ["alice"],
|
|
90
|
-
hops: 2,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
expect([...pulled].sort()).toEqual(["bob", "carol"]);
|
|
94
|
-
expect(expansions).toEqual([{ from: "alice", pulled: ["bob", "carol"] }]);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("defaults to 2 hops when hops is omitted", async () => {
|
|
98
|
-
await writeGraph({ alice: ["bob"], bob: ["carol"], carol: ["dave"] });
|
|
99
|
-
|
|
100
|
-
const { pulled } = await expandEdges({
|
|
101
|
-
workspaceDir,
|
|
102
|
-
seeds: ["alice"],
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// 2-hop reach from alice: bob (1) + carol (2); dave (3) is out of budget.
|
|
106
|
-
expect([...pulled].sort()).toEqual(["bob", "carol"]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("excludes the seed itself from pulled", async () => {
|
|
110
|
-
// Self-referential-ish: a -> b -> a would put `a` back in reach, but the
|
|
111
|
-
// seed must never appear in its own pulled set.
|
|
112
|
-
await writeGraph({ alice: ["bob"], bob: ["alice"] });
|
|
113
|
-
|
|
114
|
-
const { pulled, expansions } = await expandEdges({
|
|
115
|
-
workspaceDir,
|
|
116
|
-
seeds: ["alice"],
|
|
117
|
-
hops: 2,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
expect(pulled.has("alice")).toBe(false);
|
|
121
|
-
expect([...pulled].sort()).toEqual(["bob"]);
|
|
122
|
-
expect(expansions[0]!.pulled).not.toContain("alice");
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("orphan seed (no outgoing edges) yields an empty expansion", async () => {
|
|
126
|
-
await writeGraph({ alice: [] });
|
|
127
|
-
|
|
128
|
-
const { pulled, expansions } = await expandEdges({
|
|
129
|
-
workspaceDir,
|
|
130
|
-
seeds: ["alice"],
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
expect(pulled.size).toBe(0);
|
|
134
|
-
expect(expansions).toEqual([{ from: "alice", pulled: [] }]);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("edges are directed — incoming neighbors are never pulled", async () => {
|
|
138
|
-
// bob -> alice. Seeding alice must NOT pull bob (that's an in-edge).
|
|
139
|
-
await writeGraph({ bob: ["alice"], alice: [] });
|
|
140
|
-
|
|
141
|
-
const { pulled } = await expandEdges({
|
|
142
|
-
workspaceDir,
|
|
143
|
-
seeds: ["alice"],
|
|
144
|
-
hops: 2,
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
expect(pulled.size).toBe(0);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
// Multiple seeds
|
|
153
|
-
// ---------------------------------------------------------------------------
|
|
154
|
-
|
|
155
|
-
describe("expandEdges — multiple seeds", () => {
|
|
156
|
-
test("top-level pulled is the union across seeds", async () => {
|
|
157
|
-
await writeGraph({
|
|
158
|
-
alice: ["bob"],
|
|
159
|
-
bob: [],
|
|
160
|
-
carol: ["dave"],
|
|
161
|
-
dave: [],
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const { pulled, expansions } = await expandEdges({
|
|
165
|
-
workspaceDir,
|
|
166
|
-
seeds: ["alice", "carol"],
|
|
167
|
-
hops: 1,
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
expect([...pulled].sort()).toEqual(["bob", "dave"]);
|
|
171
|
-
expect(expansions).toEqual([
|
|
172
|
-
{ from: "alice", pulled: ["bob"] },
|
|
173
|
-
{ from: "carol", pulled: ["dave"] },
|
|
174
|
-
]);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("a slug pulled by two seeds appears once in pulled, once per expansion", async () => {
|
|
178
|
-
// alice -> shared, carol -> shared
|
|
179
|
-
await writeGraph({ alice: ["shared"], carol: ["shared"], shared: [] });
|
|
180
|
-
|
|
181
|
-
const { pulled, expansions } = await expandEdges({
|
|
182
|
-
workspaceDir,
|
|
183
|
-
seeds: ["alice", "carol"],
|
|
184
|
-
hops: 1,
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
expect([...pulled]).toEqual(["shared"]);
|
|
188
|
-
expect(expansions).toEqual([
|
|
189
|
-
{ from: "alice", pulled: ["shared"] },
|
|
190
|
-
{ from: "carol", pulled: ["shared"] },
|
|
191
|
-
]);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("duplicate seeds collapse to a single expansion entry", async () => {
|
|
195
|
-
await writeGraph({ alice: ["bob"], bob: [] });
|
|
196
|
-
|
|
197
|
-
const { expansions } = await expandEdges({
|
|
198
|
-
workspaceDir,
|
|
199
|
-
seeds: ["alice", "alice"],
|
|
200
|
-
hops: 1,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
expect(expansions).toEqual([{ from: "alice", pulled: ["bob"] }]);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("empty seed set yields an empty result", async () => {
|
|
207
|
-
await writeGraph({ alice: ["bob"], bob: [] });
|
|
208
|
-
|
|
209
|
-
const { pulled, expansions } = await expandEdges({
|
|
210
|
-
workspaceDir,
|
|
211
|
-
seeds: [],
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
expect(pulled.size).toBe(0);
|
|
215
|
-
expect(expansions).toEqual([]);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// ---------------------------------------------------------------------------
|
|
220
|
-
// extraAdjacency injection seam
|
|
221
|
-
// ---------------------------------------------------------------------------
|
|
222
|
-
|
|
223
|
-
describe("expandEdges — extraAdjacency", () => {
|
|
224
|
-
test("merges injected out-edges with the curated graph", async () => {
|
|
225
|
-
// Curated: alice -> bob. Injected: alice -> extra.
|
|
226
|
-
await writeGraph({ alice: ["bob"], bob: [], extra: [] });
|
|
227
|
-
|
|
228
|
-
const extraAdjacency = new Map<string, Set<string>>([
|
|
229
|
-
["alice", new Set(["extra"])],
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
|
-
const { pulled, expansions } = await expandEdges({
|
|
233
|
-
workspaceDir,
|
|
234
|
-
seeds: ["alice"],
|
|
235
|
-
hops: 1,
|
|
236
|
-
extraAdjacency,
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
expect([...pulled].sort()).toEqual(["bob", "extra"]);
|
|
240
|
-
expect(expansions).toEqual([{ from: "alice", pulled: ["bob", "extra"] }]);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("injected edges bridge across hops (curated -> extra -> curated)", async () => {
|
|
244
|
-
// Curated: alice -> bob, learned -> dave. Injected: bob -> learned.
|
|
245
|
-
// 2-hop reach: bob (curated, hop 1) -> learned (extra, hop 2)...
|
|
246
|
-
// and learned -> dave is hop 3, out of a 2-hop budget.
|
|
247
|
-
await writeGraph({
|
|
248
|
-
alice: ["bob"],
|
|
249
|
-
bob: [],
|
|
250
|
-
learned: ["dave"],
|
|
251
|
-
dave: [],
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const extraAdjacency = new Map<string, Set<string>>([
|
|
255
|
-
["bob", new Set(["learned"])],
|
|
256
|
-
]);
|
|
257
|
-
|
|
258
|
-
const twoHop = await expandEdges({
|
|
259
|
-
workspaceDir,
|
|
260
|
-
seeds: ["alice"],
|
|
261
|
-
hops: 2,
|
|
262
|
-
extraAdjacency,
|
|
263
|
-
});
|
|
264
|
-
expect([...twoHop.pulled].sort()).toEqual(["bob", "learned"]);
|
|
265
|
-
|
|
266
|
-
const threeHop = await expandEdges({
|
|
267
|
-
workspaceDir,
|
|
268
|
-
seeds: ["alice"],
|
|
269
|
-
hops: 3,
|
|
270
|
-
extraAdjacency,
|
|
271
|
-
});
|
|
272
|
-
expect([...threeHop.pulled].sort()).toEqual(["bob", "dave", "learned"]);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test("absent extraAdjacency leaves the curated walk unchanged", async () => {
|
|
276
|
-
await writeGraph({ alice: ["bob"], bob: ["carol"], carol: [] });
|
|
277
|
-
|
|
278
|
-
const { pulled } = await expandEdges({
|
|
279
|
-
workspaceDir,
|
|
280
|
-
seeds: ["alice"],
|
|
281
|
-
hops: 2,
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
expect([...pulled].sort()).toEqual(["bob", "carol"]);
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// ---------------------------------------------------------------------------
|
|
289
|
-
// Cycle safety
|
|
290
|
-
// ---------------------------------------------------------------------------
|
|
291
|
-
|
|
292
|
-
describe("expandEdges — cycle safety", () => {
|
|
293
|
-
test("a cycle in the curated graph terminates and does not loop", async () => {
|
|
294
|
-
// alice -> bob -> carol -> alice (3-cycle).
|
|
295
|
-
await writeGraph({
|
|
296
|
-
alice: ["bob"],
|
|
297
|
-
bob: ["carol"],
|
|
298
|
-
carol: ["alice"],
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
// A generous hop budget would loop forever without a visited set.
|
|
302
|
-
const { pulled, expansions } = await expandEdges({
|
|
303
|
-
workspaceDir,
|
|
304
|
-
seeds: ["alice"],
|
|
305
|
-
hops: 100,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Reaches bob and carol; alice (the seed) is excluded even though the
|
|
309
|
-
// cycle points back at it.
|
|
310
|
-
expect([...pulled].sort()).toEqual(["bob", "carol"]);
|
|
311
|
-
expect(expansions[0]!.pulled).not.toContain("alice");
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
test("a cycle introduced via extraAdjacency also terminates", async () => {
|
|
315
|
-
// Curated: alice -> bob. Injected cycle: bob -> alice.
|
|
316
|
-
await writeGraph({ alice: ["bob"], bob: [] });
|
|
317
|
-
|
|
318
|
-
const extraAdjacency = new Map<string, Set<string>>([
|
|
319
|
-
["bob", new Set(["alice"])],
|
|
320
|
-
]);
|
|
321
|
-
|
|
322
|
-
const { pulled } = await expandEdges({
|
|
323
|
-
workspaceDir,
|
|
324
|
-
seeds: ["alice"],
|
|
325
|
-
hops: 100,
|
|
326
|
-
extraAdjacency,
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
expect([...pulled].sort()).toEqual(["bob"]);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test("a self-loop edge does not loop or pull the seed", async () => {
|
|
333
|
-
// alice -> alice (self-loop is dropped by the index, but guard anyway).
|
|
334
|
-
await writeGraph({ alice: ["alice", "bob"], bob: [] });
|
|
335
|
-
|
|
336
|
-
const { pulled } = await expandEdges({
|
|
337
|
-
workspaceDir,
|
|
338
|
-
seeds: ["alice"],
|
|
339
|
-
hops: 2,
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
expect(pulled.has("alice")).toBe(false);
|
|
343
|
-
expect([...pulled].sort()).toEqual(["bob"]);
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// ---------------------------------------------------------------------------
|
|
348
|
-
// Expansion bounds
|
|
349
|
-
// ---------------------------------------------------------------------------
|
|
350
|
-
//
|
|
351
|
-
// These constants mirror the (intentionally module-private) caps in `edges.ts`.
|
|
352
|
-
// The seed set handed to this lane is the union of every upstream lane's
|
|
353
|
-
// candidates, so on a mature corpus it can run to thousands of slugs; expanding
|
|
354
|
-
// all of them to their full neighborhood balloons the downstream gate's pool.
|
|
355
|
-
// The lane caps the seeds expanded, the per-seed fan-out, and the total union.
|
|
356
|
-
const MAX_SEEDS_EXPANDED = 150;
|
|
357
|
-
const MAX_PULLS_PER_SEED = 32;
|
|
358
|
-
const MAX_TOTAL_PULLS = 400;
|
|
359
|
-
|
|
360
|
-
/** Zero-padded slug so lexical sort matches numeric order (`topics/000`..). */
|
|
361
|
-
function topicSlug(domain: string, i: number): string {
|
|
362
|
-
return `${domain}/${String(i).padStart(4, "0")}`;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
describe("expandEdges — bounds", () => {
|
|
366
|
-
test("expands at most MAX_SEEDS_EXPANDED seeds, dropping the tail", async () => {
|
|
367
|
-
// More seeds than the cap, each a 1-hop edge to its own private target.
|
|
368
|
-
const seedCount = MAX_SEEDS_EXPANDED + 50;
|
|
369
|
-
const graph: Record<string, string[]> = {};
|
|
370
|
-
const seeds: string[] = [];
|
|
371
|
-
for (let i = 0; i < seedCount; i++) {
|
|
372
|
-
const seed = topicSlug("people", i);
|
|
373
|
-
const target = topicSlug("targets", i);
|
|
374
|
-
graph[seed] = [target];
|
|
375
|
-
graph[target] = [];
|
|
376
|
-
seeds.push(seed);
|
|
377
|
-
}
|
|
378
|
-
await writeGraph(graph);
|
|
379
|
-
|
|
380
|
-
const { pulled, expansions } = await expandEdges({
|
|
381
|
-
workspaceDir,
|
|
382
|
-
seeds,
|
|
383
|
-
hops: 1,
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
// Only the first MAX_SEEDS_EXPANDED seeds yield an expansion entry; the
|
|
387
|
-
// 200-seed input is truncated to the cap.
|
|
388
|
-
expect(expansions).toHaveLength(MAX_SEEDS_EXPANDED);
|
|
389
|
-
expect(expansions.map((e) => e.from)).toEqual(
|
|
390
|
-
seeds.slice(0, MAX_SEEDS_EXPANDED),
|
|
391
|
-
);
|
|
392
|
-
// Each surviving seed contributes exactly its one target, so the union is
|
|
393
|
-
// one slug per expanded seed and stays under the total ceiling.
|
|
394
|
-
expect(pulled.size).toBe(MAX_SEEDS_EXPANDED);
|
|
395
|
-
expect(pulled.size).toBeLessThanOrEqual(MAX_TOTAL_PULLS);
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
test("caps a single hub seed's fan-out at MAX_PULLS_PER_SEED", async () => {
|
|
399
|
-
// One hub seed pointing at far more 1-hop neighbors than the per-seed cap.
|
|
400
|
-
const fanOut = MAX_PULLS_PER_SEED + 40;
|
|
401
|
-
const graph: Record<string, string[]> = {};
|
|
402
|
-
const targets: string[] = [];
|
|
403
|
-
for (let i = 0; i < fanOut; i++) {
|
|
404
|
-
const target = topicSlug("topics", i);
|
|
405
|
-
targets.push(target);
|
|
406
|
-
graph[target] = [];
|
|
407
|
-
}
|
|
408
|
-
graph["hub"] = targets;
|
|
409
|
-
await writeGraph(graph);
|
|
410
|
-
|
|
411
|
-
const { pulled, expansions } = await expandEdges({
|
|
412
|
-
workspaceDir,
|
|
413
|
-
seeds: ["hub"],
|
|
414
|
-
hops: 1,
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
// The hub's neighborhood is truncated to the per-seed cap, deterministically
|
|
418
|
-
// keeping the lexicographically-first targets.
|
|
419
|
-
expect(expansions).toHaveLength(1);
|
|
420
|
-
expect(expansions[0]!.from).toBe("hub");
|
|
421
|
-
expect(expansions[0]!.pulled).toHaveLength(MAX_PULLS_PER_SEED);
|
|
422
|
-
expect(expansions[0]!.pulled).toEqual(
|
|
423
|
-
[...targets].sort().slice(0, MAX_PULLS_PER_SEED),
|
|
424
|
-
);
|
|
425
|
-
expect(pulled.size).toBe(MAX_PULLS_PER_SEED);
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
test("bounds the total pulled union at MAX_TOTAL_PULLS across many seeds", async () => {
|
|
429
|
-
// Enough seeds, each with a per-seed-cap-sized private fan-out, that the
|
|
430
|
-
// unbounded union would be seedCount * MAX_PULLS_PER_SEED slugs — far past
|
|
431
|
-
// the total ceiling (ceil(400/32) = 13 seeds fills it). Each seed's targets
|
|
432
|
-
// are disjoint, so nothing collapses via de-dup and the only thing holding
|
|
433
|
-
// the union down is the cap.
|
|
434
|
-
const seedCount = 20;
|
|
435
|
-
const graph: Record<string, string[]> = {};
|
|
436
|
-
const seeds: string[] = [];
|
|
437
|
-
for (let s = 0; s < seedCount; s++) {
|
|
438
|
-
const seed = topicSlug("people", s);
|
|
439
|
-
const targets: string[] = [];
|
|
440
|
-
for (let t = 0; t < MAX_PULLS_PER_SEED; t++) {
|
|
441
|
-
// Globally-unique target slug per (seed, target) pair.
|
|
442
|
-
const target = topicSlug("targets", s * MAX_PULLS_PER_SEED + t);
|
|
443
|
-
targets.push(target);
|
|
444
|
-
graph[target] = [];
|
|
445
|
-
}
|
|
446
|
-
graph[seed] = targets;
|
|
447
|
-
seeds.push(seed);
|
|
448
|
-
}
|
|
449
|
-
await writeGraph(graph);
|
|
450
|
-
|
|
451
|
-
const { pulled, expansions } = await expandEdges({
|
|
452
|
-
workspaceDir,
|
|
453
|
-
seeds,
|
|
454
|
-
hops: 1,
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
// The union is held at the ceiling exactly — never overshot — even though
|
|
458
|
-
// the seeds collectively reach far more slugs.
|
|
459
|
-
expect(pulled.size).toBe(MAX_TOTAL_PULLS);
|
|
460
|
-
// Every emitted expansion entry lists only slugs that made it into the
|
|
461
|
-
// bounded union, so the trace never claims an un-pulled slug.
|
|
462
|
-
for (const expansion of expansions) {
|
|
463
|
-
for (const slug of expansion.pulled) {
|
|
464
|
-
expect(pulled.has(slug)).toBe(true);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test("maxTotalPulls overrides the default union ceiling", async () => {
|
|
470
|
-
// 20 seeds × 32 disjoint targets = 640 reachable, far past any cap. A low
|
|
471
|
-
// per-call maxTotalPulls must bound the union to that value, not the 400
|
|
472
|
-
// default; an omitted or invalid value falls back to the default.
|
|
473
|
-
const seedCount = 20;
|
|
474
|
-
const graph: Record<string, string[]> = {};
|
|
475
|
-
const seeds: string[] = [];
|
|
476
|
-
for (let s = 0; s < seedCount; s++) {
|
|
477
|
-
const seed = topicSlug("people", s);
|
|
478
|
-
const targets: string[] = [];
|
|
479
|
-
for (let t = 0; t < MAX_PULLS_PER_SEED; t++) {
|
|
480
|
-
const target = topicSlug("targets", s * MAX_PULLS_PER_SEED + t);
|
|
481
|
-
targets.push(target);
|
|
482
|
-
graph[target] = [];
|
|
483
|
-
}
|
|
484
|
-
graph[seed] = targets;
|
|
485
|
-
seeds.push(seed);
|
|
486
|
-
}
|
|
487
|
-
await writeGraph(graph);
|
|
488
|
-
|
|
489
|
-
const capped = await expandEdges({
|
|
490
|
-
workspaceDir,
|
|
491
|
-
seeds,
|
|
492
|
-
hops: 1,
|
|
493
|
-
maxTotalPulls: 40,
|
|
494
|
-
});
|
|
495
|
-
expect(capped.pulled.size).toBeLessThanOrEqual(40);
|
|
496
|
-
|
|
497
|
-
// Omitted → the 400 default; an invalid (negative) value also falls back.
|
|
498
|
-
const dflt = await expandEdges({ workspaceDir, seeds, hops: 1 });
|
|
499
|
-
expect(dflt.pulled.size).toBe(MAX_TOTAL_PULLS);
|
|
500
|
-
const invalid = await expandEdges({
|
|
501
|
-
workspaceDir,
|
|
502
|
-
seeds,
|
|
503
|
-
hops: 1,
|
|
504
|
-
maxTotalPulls: -5,
|
|
505
|
-
});
|
|
506
|
-
expect(invalid.pulled.size).toBe(MAX_TOTAL_PULLS);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
test("duplicate slugs across seeds don't waste the total budget", async () => {
|
|
510
|
-
// Two seeds, both pointing at the same shared target plus one private each.
|
|
511
|
-
// The shared slug is counted once, so the union is 3 — well under the cap,
|
|
512
|
-
// and both seeds still get a faithful expansion entry.
|
|
513
|
-
await writeGraph({
|
|
514
|
-
"people/alice": ["topics/shared", "topics/alice-only"],
|
|
515
|
-
"people/bob": ["topics/shared", "topics/bob-only"],
|
|
516
|
-
"topics/shared": [],
|
|
517
|
-
"topics/alice-only": [],
|
|
518
|
-
"topics/bob-only": [],
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
const { pulled, expansions } = await expandEdges({
|
|
522
|
-
workspaceDir,
|
|
523
|
-
seeds: ["people/alice", "people/bob"],
|
|
524
|
-
hops: 1,
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
expect([...pulled].sort()).toEqual([
|
|
528
|
-
"topics/alice-only",
|
|
529
|
-
"topics/bob-only",
|
|
530
|
-
"topics/shared",
|
|
531
|
-
]);
|
|
532
|
-
expect(pulled.size).toBeLessThanOrEqual(MAX_TOTAL_PULLS);
|
|
533
|
-
expect(expansions).toEqual([
|
|
534
|
-
{ from: "people/alice", pulled: ["topics/alice-only", "topics/shared"] },
|
|
535
|
-
{ from: "people/bob", pulled: ["topics/bob-only", "topics/shared"] },
|
|
536
|
-
]);
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
test("a seed reachable from another seed is excluded from pulled", async () => {
|
|
540
|
-
// alice -> bob, and bob is itself a seed. bob is a confident hit in its own
|
|
541
|
-
// right, not a neighbor, so it must never appear in the pulled union — even
|
|
542
|
-
// though alice's outgoing walk reaches it. bob's own private neighbor is
|
|
543
|
-
// still pulled.
|
|
544
|
-
await writeGraph({
|
|
545
|
-
alice: ["bob"],
|
|
546
|
-
bob: ["carol"],
|
|
547
|
-
carol: [],
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
const { pulled, expansions } = await expandEdges({
|
|
551
|
-
workspaceDir,
|
|
552
|
-
seeds: ["alice", "bob"],
|
|
553
|
-
hops: 1,
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
expect(pulled.has("bob")).toBe(false);
|
|
557
|
-
expect([...pulled].sort()).toEqual(["carol"]);
|
|
558
|
-
// alice reaches only bob, which is a seed, so alice contributes nothing.
|
|
559
|
-
expect(expansions).toEqual([
|
|
560
|
-
{ from: "alice", pulled: [] },
|
|
561
|
-
{ from: "bob", pulled: ["carol"] },
|
|
562
|
-
]);
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
test("a seed two hops from another seed is still excluded from pulled", async () => {
|
|
566
|
-
// alice -> mid -> bob. With a 2-hop walk alice reaches bob, but bob is a
|
|
567
|
-
// seed and must not leak into pulled; the intermediate `mid` is a genuine
|
|
568
|
-
// neighbor and is kept.
|
|
569
|
-
await writeGraph({
|
|
570
|
-
alice: ["mid"],
|
|
571
|
-
mid: ["bob"],
|
|
572
|
-
bob: [],
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
const { pulled } = await expandEdges({
|
|
576
|
-
workspaceDir,
|
|
577
|
-
seeds: ["alice", "bob"],
|
|
578
|
-
hops: 2,
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
expect(pulled.has("bob")).toBe(false);
|
|
582
|
-
expect([...pulled].sort()).toEqual(["mid"]);
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
test("per-seed cap is spent on unique neighbors, not duplicates", async () => {
|
|
586
|
-
// alice pulls 16 shared `dup/*` neighbors. bob reaches those same 16 dups
|
|
587
|
-
// plus 32 unique `fresh/*` neighbors. bob's per-seed budget is
|
|
588
|
-
// MAX_PULLS_PER_SEED (32) because the union is nowhere near the total cap.
|
|
589
|
-
//
|
|
590
|
-
// The old slice took bob's lexicographically-first 32 reached slugs — the
|
|
591
|
-
// 16 already-pulled dups plus only the first 16 fresh — wasting 16 budget
|
|
592
|
-
// slots on duplicates and dropping the other 16 unique neighbors. Filtering
|
|
593
|
-
// already-pulled slugs before the slice spends all 32 slots on fresh
|
|
594
|
-
// neighbors, so every unique one is retained (recall north star).
|
|
595
|
-
const dupCount = 16;
|
|
596
|
-
const freshCount = MAX_PULLS_PER_SEED; // 32, so old code drops 16 unique.
|
|
597
|
-
const dups: string[] = [];
|
|
598
|
-
const fresh: string[] = [];
|
|
599
|
-
const graph: Record<string, string[]> = {};
|
|
600
|
-
for (let i = 0; i < dupCount; i++) {
|
|
601
|
-
const slug = topicSlug("dup", i);
|
|
602
|
-
dups.push(slug);
|
|
603
|
-
graph[slug] = [];
|
|
604
|
-
}
|
|
605
|
-
for (let i = 0; i < freshCount; i++) {
|
|
606
|
-
const slug = topicSlug("fresh", i);
|
|
607
|
-
fresh.push(slug);
|
|
608
|
-
graph[slug] = [];
|
|
609
|
-
}
|
|
610
|
-
// "dup/*" sorts before "fresh/*", so the old front-of-list slice is exactly
|
|
611
|
-
// the 16 dups + first 16 fresh.
|
|
612
|
-
graph["alice"] = [...dups];
|
|
613
|
-
graph["bob"] = [...dups, ...fresh];
|
|
614
|
-
await writeGraph(graph);
|
|
615
|
-
|
|
616
|
-
const { pulled, expansions } = await expandEdges({
|
|
617
|
-
workspaceDir,
|
|
618
|
-
seeds: ["alice", "bob"],
|
|
619
|
-
hops: 1,
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
// All 32 unique fresh neighbors survive — none dropped to a duplicate slot.
|
|
623
|
-
for (const slug of fresh) expect(pulled.has(slug)).toBe(true);
|
|
624
|
-
// A unique neighbor the old code would have dropped (past the first 16) is
|
|
625
|
-
// now retained.
|
|
626
|
-
expect(pulled.has(topicSlug("fresh", freshCount - 1))).toBe(true);
|
|
627
|
-
// Union is the 16 dups + 32 fresh, all distinct.
|
|
628
|
-
expect(pulled.size).toBe(dupCount + freshCount);
|
|
629
|
-
|
|
630
|
-
// bob's budget (32 fresh slots) is fully spent on unique neighbors; the
|
|
631
|
-
// dups it also reaches stay in its trace for faithful attribution but did
|
|
632
|
-
// not consume the budget.
|
|
633
|
-
const bobExpansion = expansions.find((e) => e.from === "bob")!;
|
|
634
|
-
expect(bobExpansion.pulled).toEqual([...dups, ...fresh].sort());
|
|
635
|
-
// Every trace slug is genuinely in the union.
|
|
636
|
-
for (const slug of bobExpansion.pulled) expect(pulled.has(slug)).toBe(true);
|
|
637
|
-
});
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
// ---------------------------------------------------------------------------
|
|
641
|
-
// Seed ranking by lane trust
|
|
642
|
-
// ---------------------------------------------------------------------------
|
|
643
|
-
|
|
644
|
-
describe("expandEdges — seed ranking", () => {
|
|
645
|
-
test("ranks tree/dense/sparse seeds ahead of hot before the seed cap", async () => {
|
|
646
|
-
// 200 seeds (> the 150 cap), each with a private 1-hop edge. In candidate
|
|
647
|
-
// order the first 150 are hot (recency) and the last 50 are tree (LLM-
|
|
648
|
-
// chosen). Without ranking the cap would expand all 150 hot and drop every
|
|
649
|
-
// tree seed; with laneBySlug the tree seeds must be expanded first.
|
|
650
|
-
const hotCount = 150;
|
|
651
|
-
const treeCount = 50;
|
|
652
|
-
const graph: Record<string, string[]> = {};
|
|
653
|
-
const seeds: string[] = [];
|
|
654
|
-
const laneBySlug = new Map<string, string>();
|
|
655
|
-
for (let i = 0; i < hotCount; i++) {
|
|
656
|
-
const seed = topicSlug("hot", i);
|
|
657
|
-
graph[seed] = [topicSlug("hot-targets", i)];
|
|
658
|
-
graph[topicSlug("hot-targets", i)] = [];
|
|
659
|
-
seeds.push(seed);
|
|
660
|
-
laneBySlug.set(seed, "hot");
|
|
661
|
-
}
|
|
662
|
-
for (let i = 0; i < treeCount; i++) {
|
|
663
|
-
const seed = topicSlug("tree", i);
|
|
664
|
-
graph[seed] = [topicSlug("tree-targets", i)];
|
|
665
|
-
graph[topicSlug("tree-targets", i)] = [];
|
|
666
|
-
seeds.push(seed);
|
|
667
|
-
laneBySlug.set(seed, "tree");
|
|
668
|
-
}
|
|
669
|
-
await writeGraph(graph);
|
|
670
|
-
|
|
671
|
-
const { expansions } = await expandEdges({
|
|
672
|
-
workspaceDir,
|
|
673
|
-
seeds,
|
|
674
|
-
hops: 1,
|
|
675
|
-
laneBySlug,
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
const expandedFrom = expansions.map((e) => e.from);
|
|
679
|
-
// The seed cap still holds.
|
|
680
|
-
expect(expandedFrom).toHaveLength(MAX_SEEDS_EXPANDED);
|
|
681
|
-
// Every tree seed survives the cap (ranked ahead of hot) and leads the
|
|
682
|
-
// order, keeping candidate order within the tier (stable sort).
|
|
683
|
-
const treeSeeds = seeds.slice(hotCount);
|
|
684
|
-
expect(expandedFrom.slice(0, treeCount)).toEqual(treeSeeds);
|
|
685
|
-
// The dropped seeds are the tail-end hot seeds, not any tree seed.
|
|
686
|
-
const droppedHot = seeds.slice(hotCount - treeCount, hotCount);
|
|
687
|
-
for (const h of droppedHot) expect(expandedFrom).not.toContain(h);
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
test("without laneBySlug, seeds keep candidate order", async () => {
|
|
691
|
-
const graph: Record<string, string[]> = {};
|
|
692
|
-
const seeds: string[] = [];
|
|
693
|
-
for (let i = 0; i < MAX_SEEDS_EXPANDED + 10; i++) {
|
|
694
|
-
const seed = topicSlug("people", i);
|
|
695
|
-
graph[seed] = [topicSlug("targets", i)];
|
|
696
|
-
graph[topicSlug("targets", i)] = [];
|
|
697
|
-
seeds.push(seed);
|
|
698
|
-
}
|
|
699
|
-
await writeGraph(graph);
|
|
700
|
-
|
|
701
|
-
const { expansions } = await expandEdges({ workspaceDir, seeds, hops: 1 });
|
|
702
|
-
expect(expansions.map((e) => e.from)).toEqual(
|
|
703
|
-
seeds.slice(0, MAX_SEEDS_EXPANDED),
|
|
704
|
-
);
|
|
705
|
-
});
|
|
706
|
-
});
|