@vellumai/assistant 0.8.12 → 0.9.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +219 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/mock-gateway-ipc.ts +23 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/resolve-app-id.test.ts +56 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/bundler/package-resolver.ts +0 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +167 -3
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +28 -11
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +152 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +91 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
* by `pinned_ids`; out-of-range IDs dropped; selections deduped by slug
|
|
14
14
|
* (a page can appear as both a card and a finder line; pinned flags OR).
|
|
15
15
|
* - Omitted `ids` → keep ALL candidates (recall-safe, slug-deduped).
|
|
16
|
-
* - Explicit `ids: []` → keep none (deliberate abstention).
|
|
16
|
+
* - Explicit `ids: []` → keep none (deliberate abstention) — a normal result.
|
|
17
|
+
* - Empty candidate pool → keep none (nothing to select).
|
|
17
18
|
* - Finder snippets are whitespace-collapsed and truncated (~300 chars).
|
|
18
|
-
* - No provider / missing tool_use / schema mismatch / throw →
|
|
19
|
-
* (
|
|
19
|
+
* - No provider / missing tool_use / schema mismatch / provider throw → throw
|
|
20
|
+
* MemoryV3RetrievalUnavailableError (an INFRA failure, deliberately DISTINCT
|
|
21
|
+
* from a deliberate empty selection), the last three after a re-prompt retry.
|
|
20
22
|
* - One forced-tool `select_pages` call on the v3 L2 call site with
|
|
21
23
|
* `disableTurnStartCache` (the tail varies per turn — the provider's
|
|
22
24
|
* auto-anchor would never hit).
|
|
@@ -61,7 +63,8 @@ mock.module("../../../../util/logger.js", () => ({
|
|
|
61
63
|
}),
|
|
62
64
|
}));
|
|
63
65
|
|
|
64
|
-
const { selectPool } =
|
|
66
|
+
const { selectPool, MemoryV3RetrievalUnavailableError } =
|
|
67
|
+
await import("../pool-select.js");
|
|
65
68
|
type SelectorPool = Parameters<typeof selectPool>[0];
|
|
66
69
|
|
|
67
70
|
// ---------------------------------------------------------------------------
|
|
@@ -226,35 +229,42 @@ describe("selectPool — id mapping", () => {
|
|
|
226
229
|
});
|
|
227
230
|
|
|
228
231
|
// ---------------------------------------------------------------------------
|
|
229
|
-
// selectPool —
|
|
232
|
+
// selectPool — infrastructure failures THROW (no silent degradation). A
|
|
233
|
+
// deliberate empty selection and an empty pool (covered above) still return
|
|
234
|
+
// normally; only a genuine infra failure throws so the LIVE injector can
|
|
235
|
+
// hard-fail the turn instead of shipping it with no memory.
|
|
230
236
|
// ---------------------------------------------------------------------------
|
|
231
237
|
|
|
232
|
-
describe("selectPool —
|
|
233
|
-
test("no provider →
|
|
238
|
+
describe("selectPool — infrastructure failures throw", () => {
|
|
239
|
+
test("no provider → throws without calling the provider", async () => {
|
|
234
240
|
providerStub = null;
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
242
|
+
MemoryV3RetrievalUnavailableError,
|
|
243
|
+
);
|
|
237
244
|
expect(providerCalls).toHaveLength(0);
|
|
238
245
|
});
|
|
239
246
|
|
|
240
|
-
test("missing tool_use →
|
|
247
|
+
test("missing tool_use → throws after retrying", async () => {
|
|
241
248
|
providerStub = makeProvider(noToolResponse());
|
|
242
|
-
|
|
243
|
-
|
|
249
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
250
|
+
MemoryV3RetrievalUnavailableError,
|
|
251
|
+
);
|
|
244
252
|
expect(providerCalls).toHaveLength(3);
|
|
245
253
|
});
|
|
246
254
|
|
|
247
|
-
test("schema mismatch →
|
|
255
|
+
test("schema mismatch → throws after retrying", async () => {
|
|
248
256
|
providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
|
|
249
|
-
|
|
250
|
-
|
|
257
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
258
|
+
MemoryV3RetrievalUnavailableError,
|
|
259
|
+
);
|
|
251
260
|
expect(providerCalls).toHaveLength(3);
|
|
252
261
|
});
|
|
253
262
|
|
|
254
|
-
test("provider throw →
|
|
263
|
+
test("provider throw → throws after retrying", async () => {
|
|
255
264
|
providerStub = makeThrowingProvider();
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
266
|
+
MemoryV3RetrievalUnavailableError,
|
|
267
|
+
);
|
|
258
268
|
expect(providerCalls).toHaveLength(3);
|
|
259
269
|
});
|
|
260
270
|
|
|
@@ -45,6 +45,12 @@ type ScrollPage = {
|
|
|
45
45
|
next_page_offset: string | number | null;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
// A getCollection() response for an existing collection whose single dense
|
|
49
|
+
// vector matches the configured dimension (CONFIG.vectorSize === 4).
|
|
50
|
+
const MATCHING_SECTION_SCHEMA = {
|
|
51
|
+
config: { params: { vectors: { size: 4, distance: "Cosine" } } },
|
|
52
|
+
};
|
|
53
|
+
|
|
48
54
|
const state = {
|
|
49
55
|
collectionExists: false,
|
|
50
56
|
collectionExistsCalls: 0,
|
|
@@ -54,6 +60,10 @@ const state = {
|
|
|
54
60
|
upsertCalls: [] as Array<{ wait: boolean; points: MockPoint[] }>,
|
|
55
61
|
deleteCalls: [] as Array<{ wait: boolean; filter: unknown }>,
|
|
56
62
|
createCollectionThrows: null as Error | null,
|
|
63
|
+
getCollectionCalls: 0,
|
|
64
|
+
getCollectionInfo: MATCHING_SECTION_SCHEMA as unknown,
|
|
65
|
+
getCollectionThrows: null as Error | null,
|
|
66
|
+
deleteCollectionCalls: [] as string[],
|
|
57
67
|
// Programmed `scroll` pages, consumed in order; each `scroll` call shifts one.
|
|
58
68
|
scrollPages: [] as ScrollPage[],
|
|
59
69
|
scrollCalls: [] as Array<{ limit: number; offset: unknown }>,
|
|
@@ -72,6 +82,16 @@ class MockQdrantClient {
|
|
|
72
82
|
state.collectionExists = true;
|
|
73
83
|
return {};
|
|
74
84
|
}
|
|
85
|
+
async getCollection(_name: string) {
|
|
86
|
+
state.getCollectionCalls++;
|
|
87
|
+
if (state.getCollectionThrows) throw state.getCollectionThrows;
|
|
88
|
+
return state.getCollectionInfo;
|
|
89
|
+
}
|
|
90
|
+
async deleteCollection(name: string) {
|
|
91
|
+
state.deleteCollectionCalls.push(name);
|
|
92
|
+
state.collectionExists = false;
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
75
95
|
async createPayloadIndex(
|
|
76
96
|
_name: string,
|
|
77
97
|
params: { field_name: string; field_schema: string },
|
|
@@ -131,6 +151,10 @@ function resetState(): void {
|
|
|
131
151
|
state.upsertCalls.length = 0;
|
|
132
152
|
state.deleteCalls.length = 0;
|
|
133
153
|
state.createCollectionThrows = null;
|
|
154
|
+
state.getCollectionCalls = 0;
|
|
155
|
+
state.getCollectionInfo = MATCHING_SECTION_SCHEMA;
|
|
156
|
+
state.getCollectionThrows = null;
|
|
157
|
+
state.deleteCollectionCalls.length = 0;
|
|
134
158
|
state.scrollPages.length = 0;
|
|
135
159
|
state.scrollCalls.length = 0;
|
|
136
160
|
embedState.calls.length = 0;
|
|
@@ -197,6 +221,49 @@ describe("memory v3 section-dense-store — collection lifecycle", () => {
|
|
|
197
221
|
]);
|
|
198
222
|
});
|
|
199
223
|
|
|
224
|
+
test("recreates the collection when its dense vector dimension drifts", async () => {
|
|
225
|
+
state.collectionExists = true;
|
|
226
|
+
// Existing collection sized to a different embedding dimension than the
|
|
227
|
+
// configured 4 (e.g. a 384-dim collection from a prior model). Every upsert
|
|
228
|
+
// would fail with HTTP 400 until it is recreated; the next backfill
|
|
229
|
+
// re-embeds the sections.
|
|
230
|
+
state.getCollectionInfo = {
|
|
231
|
+
config: { params: { vectors: { size: 384, distance: "Cosine" } } },
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
await ensureSectionCollection(CONFIG);
|
|
235
|
+
|
|
236
|
+
expect(state.getCollectionCalls).toBe(1);
|
|
237
|
+
expect(state.deleteCollectionCalls).toEqual([SECTION_COLLECTION]);
|
|
238
|
+
expect(state.createCollectionCalls).toBe(1);
|
|
239
|
+
// The recreated collection is sized to the configured dimension.
|
|
240
|
+
const params = state.createCollectionParams as {
|
|
241
|
+
vectors: { size: number };
|
|
242
|
+
};
|
|
243
|
+
expect(params.vectors.size).toBe(4);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("leaves a dimension-matched existing collection untouched", async () => {
|
|
247
|
+
state.collectionExists = true;
|
|
248
|
+
// Default getCollectionInfo (MATCHING_SECTION_SCHEMA) is size 4 === CONFIG.
|
|
249
|
+
|
|
250
|
+
await ensureSectionCollection(CONFIG);
|
|
251
|
+
|
|
252
|
+
expect(state.getCollectionCalls).toBe(1);
|
|
253
|
+
expect(state.deleteCollectionCalls).toEqual([]);
|
|
254
|
+
expect(state.createCollectionCalls).toBe(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("treats a getCollection probe failure as compatible (no destructive recreate)", async () => {
|
|
258
|
+
state.collectionExists = true;
|
|
259
|
+
state.getCollectionThrows = new Error("transient REST error");
|
|
260
|
+
|
|
261
|
+
await ensureSectionCollection(CONFIG);
|
|
262
|
+
|
|
263
|
+
expect(state.deleteCollectionCalls).toEqual([]);
|
|
264
|
+
expect(state.createCollectionCalls).toBe(0);
|
|
265
|
+
});
|
|
266
|
+
|
|
200
267
|
test("re-running ensure latches readiness (single existence probe)", async () => {
|
|
201
268
|
state.collectionExists = false;
|
|
202
269
|
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for `
|
|
2
|
+
* Tests for `selection-log-store.ts`.
|
|
3
3
|
*
|
|
4
4
|
* Asserts the inspector read path over `memory_v3_selections`:
|
|
5
|
-
* - the selection for the exact turn;
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
5
|
+
* - the selection for the exact turn (turn-keyed variant);
|
|
6
|
+
* - the selection keyed by the turn's message ids (the route's path), which
|
|
7
|
+
* is robust against v2/v3 turn-counter drift and does not match rows that
|
|
8
|
+
* predate the message-id backfill;
|
|
9
|
+
* - null for a null turn, empty message ids, or no matching rows;
|
|
10
|
+
* - NO fallback to another turn/message;
|
|
11
|
+
* - source/pinned/section mapping and the rendered `<memory>` block;
|
|
10
12
|
* - `live` / `shadow` reflect the flag resolver.
|
|
11
13
|
*
|
|
12
14
|
* `mock.module` is process-global and leaks into sibling files in a
|
|
13
|
-
* `bun test
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* `bun test <dir>` run, so every stub DELEGATES to the real implementation
|
|
16
|
+
* unless this test is actively running (`storeMockActive`, toggled in
|
|
17
|
+
* beforeEach/afterAll). Mirrors `shadow-plugin.test.ts`.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
import { Database } from "bun:sqlite";
|
|
@@ -21,6 +23,7 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
21
23
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
22
24
|
|
|
23
25
|
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
26
|
+
import { migrateMemoryV3SelectionsMessageIdAndSections } from "../../../../memory/migrations/283-memory-v3-selections-message-id-and-sections.js";
|
|
24
27
|
import * as schema from "../../../../memory/schema.js";
|
|
25
28
|
|
|
26
29
|
const realFlags = {
|
|
@@ -41,16 +44,27 @@ function makeDb() {
|
|
|
41
44
|
testSqlite.exec("PRAGMA journal_mode=WAL");
|
|
42
45
|
const db = drizzle(testSqlite, { schema });
|
|
43
46
|
migrateAddMemoryV3Selections(db);
|
|
47
|
+
migrateMemoryV3SelectionsMessageIdAndSections(db);
|
|
44
48
|
return db;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
function seed(
|
|
48
52
|
conversationId: string,
|
|
49
53
|
turn: number,
|
|
50
|
-
rows: Array<{
|
|
54
|
+
rows: Array<{
|
|
55
|
+
slug: string;
|
|
56
|
+
source: string;
|
|
57
|
+
pinned?: boolean;
|
|
58
|
+
sectionOrdinal?: number;
|
|
59
|
+
sectionTitle?: string;
|
|
60
|
+
}>,
|
|
61
|
+
messageId: string | null = null,
|
|
51
62
|
): void {
|
|
52
63
|
const stmt = testSqlite.query(
|
|
53
|
-
`INSERT INTO memory_v3_selections
|
|
64
|
+
`INSERT INTO memory_v3_selections
|
|
65
|
+
(conversation_id, turn, slug, source, pinned, created_at,
|
|
66
|
+
message_id, section_ordinal, section_title)
|
|
67
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
54
68
|
);
|
|
55
69
|
for (const r of rows) {
|
|
56
70
|
stmt.run(
|
|
@@ -60,6 +74,9 @@ function seed(
|
|
|
60
74
|
r.source,
|
|
61
75
|
r.pinned ? 1 : 0,
|
|
62
76
|
1000 + turn,
|
|
77
|
+
messageId,
|
|
78
|
+
r.sectionOrdinal ?? null,
|
|
79
|
+
r.sectionTitle ?? null,
|
|
63
80
|
);
|
|
64
81
|
}
|
|
65
82
|
}
|
|
@@ -97,17 +114,23 @@ mock.module("../../../../memory/db-connection.js", () => ({
|
|
|
97
114
|
|
|
98
115
|
mock.module("../page-content.js", () => ({
|
|
99
116
|
...realPageContent,
|
|
100
|
-
// The inspector store
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
117
|
+
// The inspector store reconstructs each selection's matched section from the
|
|
118
|
+
// current page; in this unit the test pages don't exist on disk, so the
|
|
119
|
+
// section map is empty and the renderer falls back to the full page. The mock
|
|
120
|
+
// stands in for that render and reflects whether a section was supplied.
|
|
121
|
+
renderV3SectionContent: async (slug: string, section?: { title: string }) =>
|
|
104
122
|
storeMockActive
|
|
105
|
-
?
|
|
123
|
+
? section
|
|
124
|
+
? `section[${section.title}] for ${slug}`
|
|
125
|
+
: `body for ${slug}`
|
|
106
126
|
: realPageContent.renderV3SectionContent(slug, undefined),
|
|
107
127
|
}));
|
|
108
128
|
|
|
109
|
-
const {
|
|
110
|
-
|
|
129
|
+
const {
|
|
130
|
+
getMemoryV3SelectionForInspector,
|
|
131
|
+
getMemoryV3SelectionForInspectorByMessageIds,
|
|
132
|
+
summarizeSelections,
|
|
133
|
+
} = await import("../selection-log-store.js");
|
|
111
134
|
|
|
112
135
|
beforeEach(() => {
|
|
113
136
|
storeMockActive = true;
|
|
@@ -150,9 +173,11 @@ describe("getMemoryV3SelectionForInspector", () => {
|
|
|
150
173
|
expect(await getMemoryV3SelectionForInspector("conv-3", 3)).toBeNull();
|
|
151
174
|
});
|
|
152
175
|
|
|
153
|
-
test("maps source/pinned and renders the <memory> block", async () => {
|
|
176
|
+
test("maps source/pinned/section and renders the <memory> block", async () => {
|
|
154
177
|
// The second row carries a retired free-text source label (the column is
|
|
155
|
-
// permissive); the inspector passes it through verbatim.
|
|
178
|
+
// permissive); the inspector passes it through verbatim. Neither row has a
|
|
179
|
+
// matched section, so section fields are null and the block falls back to
|
|
180
|
+
// full pages.
|
|
156
181
|
seed("conv-4", 1, [
|
|
157
182
|
{ slug: "domain-a/page-1", source: "edge", pinned: true },
|
|
158
183
|
{ slug: "domain-b/page-2", source: "legacy-carry", pinned: false },
|
|
@@ -160,8 +185,20 @@ describe("getMemoryV3SelectionForInspector", () => {
|
|
|
160
185
|
|
|
161
186
|
const log = await getMemoryV3SelectionForInspector("conv-4", 1);
|
|
162
187
|
expect(log?.selections).toEqual([
|
|
163
|
-
{
|
|
164
|
-
|
|
188
|
+
{
|
|
189
|
+
slug: "domain-a/page-1",
|
|
190
|
+
source: "edge",
|
|
191
|
+
pinned: true,
|
|
192
|
+
sectionOrdinal: null,
|
|
193
|
+
sectionHeading: null,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
slug: "domain-b/page-2",
|
|
197
|
+
source: "legacy-carry",
|
|
198
|
+
pinned: false,
|
|
199
|
+
sectionOrdinal: null,
|
|
200
|
+
sectionHeading: null,
|
|
201
|
+
},
|
|
165
202
|
]);
|
|
166
203
|
expect(log?.injectedText).toContain("<memory>");
|
|
167
204
|
expect(log?.injectedText).toContain("body for domain-a/page-1");
|
|
@@ -183,6 +220,69 @@ describe("getMemoryV3SelectionForInspector", () => {
|
|
|
183
220
|
});
|
|
184
221
|
});
|
|
185
222
|
|
|
223
|
+
describe("getMemoryV3SelectionForInspectorByMessageIds", () => {
|
|
224
|
+
test("returns the turn's selection (with section fields) keyed by message id", async () => {
|
|
225
|
+
seed(
|
|
226
|
+
"conv-m",
|
|
227
|
+
0,
|
|
228
|
+
[
|
|
229
|
+
{
|
|
230
|
+
slug: "domain-a/page-1",
|
|
231
|
+
source: "needle",
|
|
232
|
+
sectionOrdinal: 2,
|
|
233
|
+
sectionTitle: "Heading A",
|
|
234
|
+
},
|
|
235
|
+
{ slug: "domain-b/page-2", source: "core" },
|
|
236
|
+
],
|
|
237
|
+
"msg-assistant-1",
|
|
238
|
+
);
|
|
239
|
+
// A different turn under a different message must not bleed in.
|
|
240
|
+
seed(
|
|
241
|
+
"conv-m",
|
|
242
|
+
1,
|
|
243
|
+
[{ slug: "domain-c/page-9", source: "dense" }],
|
|
244
|
+
"msg-assistant-2",
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const log = await getMemoryV3SelectionForInspectorByMessageIds([
|
|
248
|
+
"msg-assistant-1",
|
|
249
|
+
]);
|
|
250
|
+
expect(log?.turn).toBe(0);
|
|
251
|
+
expect(log?.selections).toEqual([
|
|
252
|
+
{
|
|
253
|
+
slug: "domain-a/page-1",
|
|
254
|
+
source: "needle",
|
|
255
|
+
pinned: false,
|
|
256
|
+
sectionOrdinal: 2,
|
|
257
|
+
sectionHeading: "Heading A",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
slug: "domain-b/page-2",
|
|
261
|
+
source: "core",
|
|
262
|
+
pinned: false,
|
|
263
|
+
sectionOrdinal: null,
|
|
264
|
+
sectionHeading: null,
|
|
265
|
+
},
|
|
266
|
+
]);
|
|
267
|
+
expect(log?.injectedText).toContain("<memory>");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("returns null for empty message ids and for an unmatched id", async () => {
|
|
271
|
+
seed("conv-m", 0, [{ slug: "domain-a/page-1", source: "needle" }], "msg-1");
|
|
272
|
+
expect(await getMemoryV3SelectionForInspectorByMessageIds([])).toBeNull();
|
|
273
|
+
expect(
|
|
274
|
+
await getMemoryV3SelectionForInspectorByMessageIds(["nope"]),
|
|
275
|
+
).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("does not match rows that predate the message-id backfill (null message_id)", async () => {
|
|
279
|
+
seed("conv-m", 0, [{ slug: "domain-a/page-1", source: "needle" }]); // message_id null
|
|
280
|
+
expect(
|
|
281
|
+
await getMemoryV3SelectionForInspectorByMessageIds(["any"]),
|
|
282
|
+
).toBeNull();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
186
286
|
describe("summarizeSelections", () => {
|
|
187
287
|
test("aggregates per-source counts, turn count, and distinct slugs", () => {
|
|
188
288
|
// Turn 1: a needle + an edge selection.
|
|
@@ -34,6 +34,7 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
34
34
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
35
35
|
|
|
36
36
|
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
37
|
+
import { migrateMemoryV3SelectionsMessageIdAndSections } from "../../../../memory/migrations/283-memory-v3-selections-message-id-and-sections.js";
|
|
37
38
|
import * as schema from "../../../../memory/schema.js";
|
|
38
39
|
import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
|
|
39
40
|
import type {
|
|
@@ -105,6 +106,7 @@ function makeDb() {
|
|
|
105
106
|
testSqlite.exec("PRAGMA journal_mode=WAL");
|
|
106
107
|
const db = drizzle(testSqlite, { schema });
|
|
107
108
|
migrateAddMemoryV3Selections(db);
|
|
109
|
+
migrateMemoryV3SelectionsMessageIdAndSections(db);
|
|
108
110
|
return db;
|
|
109
111
|
}
|
|
110
112
|
mock.module("../../../../memory/db-connection.js", () => ({
|
|
@@ -29,6 +29,7 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
|
29
29
|
|
|
30
30
|
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
31
31
|
import { migrateAddMemoryV3EverInjected } from "../../../../memory/migrations/277-add-memory-v3-ever-injected.js";
|
|
32
|
+
import { migrateMemoryV3SelectionsMessageIdAndSections } from "../../../../memory/migrations/283-memory-v3-selections-message-id-and-sections.js";
|
|
32
33
|
import * as schema from "../../../../memory/schema.js";
|
|
33
34
|
import type { HotSetEntry, HotSetOptions } from "../hot-set.js";
|
|
34
35
|
import type { OrchestrateResult } from "../orchestrate.js";
|
|
@@ -154,6 +155,7 @@ function makeDb() {
|
|
|
154
155
|
testSqlite.exec("PRAGMA journal_mode=WAL");
|
|
155
156
|
const db = drizzle(testSqlite, { schema });
|
|
156
157
|
migrateAddMemoryV3Selections(db);
|
|
158
|
+
migrateMemoryV3SelectionsMessageIdAndSections(db);
|
|
157
159
|
// The live injector's net-new dedup reads/writes the everInjected store.
|
|
158
160
|
migrateAddMemoryV3EverInjected(db);
|
|
159
161
|
return db;
|
|
@@ -386,6 +388,7 @@ const {
|
|
|
386
388
|
} = await import("../shadow-plugin.js");
|
|
387
389
|
const { memoryV3Injector, resetMemoryV3InjectorStateForTests } =
|
|
388
390
|
await import("../injector.js");
|
|
391
|
+
const { MemoryV3RetrievalUnavailableError } = await import("../pool-select.js");
|
|
389
392
|
|
|
390
393
|
afterAll(() => {
|
|
391
394
|
shadowMockActive = false;
|
|
@@ -545,7 +548,15 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
545
548
|
finder: [{ slug: "page-core", descriptor: "", lane: "needle" }],
|
|
546
549
|
},
|
|
547
550
|
});
|
|
548
|
-
expect(rows).toEqual([
|
|
551
|
+
expect(rows).toEqual([
|
|
552
|
+
{
|
|
553
|
+
slug: "page-core",
|
|
554
|
+
source: "core",
|
|
555
|
+
pinned: 0,
|
|
556
|
+
sectionOrdinal: null,
|
|
557
|
+
sectionTitle: null,
|
|
558
|
+
},
|
|
559
|
+
]);
|
|
549
560
|
});
|
|
550
561
|
|
|
551
562
|
test("the turn passed to orchestrate carries the latest user message", async () => {
|
|
@@ -795,3 +806,54 @@ describe("memory-v3 shadow plugin", () => {
|
|
|
795
806
|
});
|
|
796
807
|
});
|
|
797
808
|
});
|
|
809
|
+
|
|
810
|
+
describe("memory-v3 infrastructure-failure handling (hard-fail vs swallow)", () => {
|
|
811
|
+
const throwInfra = () =>
|
|
812
|
+
orchestrateSpy.mockImplementationOnce(async () => {
|
|
813
|
+
throw new MemoryV3RetrievalUnavailableError(
|
|
814
|
+
"selector provider unavailable",
|
|
815
|
+
);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
test("LIVE injector HARD-FAILS the turn on an infra failure (no silent memory loss)", async () => {
|
|
819
|
+
liveEnabled = true;
|
|
820
|
+
shadowEnabled = false;
|
|
821
|
+
throwInfra();
|
|
822
|
+
|
|
823
|
+
await expect(produce("conv-infra-live", 0)).rejects.toThrow(
|
|
824
|
+
MemoryV3RetrievalUnavailableError,
|
|
825
|
+
);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
test("SHADOW injector swallows an infra failure (v2 fallback) — no throw, no block", async () => {
|
|
829
|
+
liveEnabled = false;
|
|
830
|
+
shadowEnabled = true;
|
|
831
|
+
throwInfra();
|
|
832
|
+
|
|
833
|
+
// Shadow mode: v2 retrieval still ran this turn, so the v3 injector returns
|
|
834
|
+
// null rather than failing the turn.
|
|
835
|
+
expect(await produce("conv-infra-shadow", 0)).toBeNull();
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
test("runShadowObservation never throws on an infra failure (fire-and-forget)", async () => {
|
|
839
|
+
liveEnabled = false;
|
|
840
|
+
shadowEnabled = true;
|
|
841
|
+
throwInfra();
|
|
842
|
+
|
|
843
|
+
await expect(
|
|
844
|
+
runShadowObservation("conv-infra-obs", 0),
|
|
845
|
+
).resolves.toBeUndefined();
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("LIVE injector stays NON-fatal on a non-infra error (degrades to no v3 block)", async () => {
|
|
849
|
+
liveEnabled = true;
|
|
850
|
+
shadowEnabled = false;
|
|
851
|
+
orchestrateSpy.mockImplementationOnce(async () => {
|
|
852
|
+
throw new Error("some unexpected non-infra bug");
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// Only INFRA failures hard-fail; any other error stays non-fatal so a bug
|
|
856
|
+
// in one lane can't take every turn down.
|
|
857
|
+
expect(await produce("conv-nonfatal-live", 0)).toBeNull();
|
|
858
|
+
});
|
|
859
|
+
});
|
|
@@ -24,17 +24,22 @@
|
|
|
24
24
|
* all-repeat turn returns an EMPTY-TEXT block: assembly attaches nothing,
|
|
25
25
|
* but the block's presence still keys v2 suppression (v3 ran and owns the
|
|
26
26
|
* `<memory>` layer this turn). A `null` return (failure / empty selection /
|
|
27
|
-
* every net-new card rendered empty)
|
|
28
|
-
* v2
|
|
27
|
+
* every net-new card rendered empty) attaches no v3 block: in shadow mode
|
|
28
|
+
* (`memory-v3-live` off) v2 still ran this turn, so its block stays as the
|
|
29
|
+
* fallback; under `memory-v3-live` the user-prompt-submit hook skips v2
|
|
30
|
+
* retrieval entirely, so a null return leaves the turn with no NEW injected
|
|
31
|
+
* memory (prior turns' frozen cards still ride history).
|
|
29
32
|
*
|
|
30
33
|
* - {@link memoryV3SpotlightInjector} (id `memory-v3-spotlight`,
|
|
31
|
-
* `
|
|
34
|
+
* `after-memory-prefix`): the EPHEMERAL layer. Renders the top `spotlight.n`
|
|
32
35
|
* selected finder hits' matched sections, plus the previous
|
|
33
36
|
* `spotlight.windowTurns` turns' entries from an in-memory per-conversation
|
|
34
37
|
* ring (a daemon restart simply re-warms it), as a `<memory_spotlight>`
|
|
35
|
-
* block
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
+
* block spliced immediately after the `<memory>` cards block (so the two
|
|
39
|
+
* memory layers sit adjacent in the prefix, ahead of the user's message
|
|
40
|
+
* text). Assembly strip-and-replaces this block every turn (scoped to this
|
|
41
|
+
* block id only); it is never persisted to metadata, so the frozen card
|
|
42
|
+
* prefix it follows stays byte-stable and cached regardless.
|
|
38
43
|
*
|
|
39
44
|
* Flag gating is unchanged: `memory-v3-live` attaches blocks; `memory-v3-shadow`
|
|
40
45
|
* (live off) logs what WOULD inject (net-new slugs + bytes + spotlight refs)
|
|
@@ -73,6 +78,7 @@ import { cardBytes } from "./card.js";
|
|
|
73
78
|
import { getActiveSlugs, recordInjected } from "./ever-injected-store.js";
|
|
74
79
|
import type { OrchestrateResult } from "./orchestrate.js";
|
|
75
80
|
import { renderV3CardContent } from "./page-content.js";
|
|
81
|
+
import { MemoryV3RetrievalUnavailableError } from "./pool-select.js";
|
|
76
82
|
import { schedulePruneValve } from "./prune.js";
|
|
77
83
|
import {
|
|
78
84
|
renderCardsBlockInner,
|
|
@@ -238,11 +244,32 @@ export const memoryV3Injector: Injector = {
|
|
|
238
244
|
if (!live && !shadow) return null;
|
|
239
245
|
if (!isPersonalMemoryAllowed(ctx.trust)) return null;
|
|
240
246
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
247
|
+
let observed: OrchestrateResult | null;
|
|
248
|
+
try {
|
|
249
|
+
observed = await observeTurnOnce(ctx.conversationId, ctx.turnIndex);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
// A memory-v3 INFRASTRUCTURE failure (the selector lost its provider —
|
|
252
|
+
// e.g. a transient CES credential blip). Under `memory-v3-live` the
|
|
253
|
+
// user-prompt-submit hook already skipped v2 retrieval, so swallowing
|
|
254
|
+
// here would ship the turn with NO memory at all — exactly the silent
|
|
255
|
+
// degradation we want to eliminate. Hard-fail the turn instead (a clean,
|
|
256
|
+
// retryable error). In shadow mode v2 still ran this turn, so fall back
|
|
257
|
+
// to it (return null). Non-infra errors are already swallowed inside
|
|
258
|
+
// observeTurn; anything else reaching here stays non-fatal.
|
|
259
|
+
if (live && err instanceof MemoryV3RetrievalUnavailableError) {
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
// Empty selection → return null (attach nothing). Returning null (vs an
|
|
265
|
+
// empty block) preserves the shadow-mode v2 fallback: v2 suppression keys
|
|
266
|
+
// off BOTH the flag AND a produced block, so in shadow a turn with nothing
|
|
267
|
+
// selected ships v2's memory. Under `memory-v3-live` the hook skipped v2
|
|
268
|
+
// retrieval, so the turn simply gets no injected memory.
|
|
269
|
+
if (!observed || observed.selections.length === 0) return null;
|
|
270
|
+
// `const` so the non-null narrowing survives capture in the `commit`
|
|
271
|
+
// closure below (a `let` would re-widen to `OrchestrateResult | null`).
|
|
272
|
+
const result = observed;
|
|
246
273
|
|
|
247
274
|
try {
|
|
248
275
|
const active = getActiveSlugs(ctx.conversationId);
|
|
@@ -258,11 +285,13 @@ export const memoryV3Injector: Injector = {
|
|
|
258
285
|
const card = await renderV3CardContent(slug);
|
|
259
286
|
if (card.trim().length > 0) cards.push({ slug, card });
|
|
260
287
|
}
|
|
261
|
-
// Every net-new card rendered empty: return null
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
288
|
+
// Every net-new card rendered empty: return null rather than an
|
|
289
|
+
// empty-text block. Returning null preserves the shadow-mode v2 fallback
|
|
290
|
+
// (an empty block would key v2 suppression yet show nothing); under
|
|
291
|
+
// `memory-v3-live` there is no v2 block, so the turn simply gets no new
|
|
292
|
+
// memory. Distinct from the all-repeat case (empty `netNew`), where the
|
|
293
|
+
// empty block correctly keeps v2 suppressed because the cards already
|
|
294
|
+
// ride history.
|
|
266
295
|
if (netNew.length > 0 && cards.length === 0) return null;
|
|
267
296
|
const entries = cards.map(({ slug, card }) => ({
|
|
268
297
|
slug,
|
|
@@ -332,7 +361,7 @@ export const memoryV3Injector: Injector = {
|
|
|
332
361
|
err: err instanceof Error ? err.message : String(err),
|
|
333
362
|
conversationId: ctx.conversationId,
|
|
334
363
|
},
|
|
335
|
-
"memory-v3 live render failed (non-fatal) —
|
|
364
|
+
"memory-v3 live render failed (non-fatal) — returning null (no v3 block this turn)",
|
|
336
365
|
);
|
|
337
366
|
return null;
|
|
338
367
|
}
|
|
@@ -369,9 +398,23 @@ export const memoryV3SpotlightInjector: Injector = {
|
|
|
369
398
|
return {
|
|
370
399
|
id: MEMORY_V3_SPOTLIGHT_BLOCK_ID,
|
|
371
400
|
text: wrapMemorySpotlightBlock(renderSpotlightInner(window)),
|
|
372
|
-
|
|
401
|
+
// Splices right after the `<memory>` cards block (this injector's
|
|
402
|
+
// order 1001 runs after the cards' 1000, and `countMemoryPrefixBlocks`
|
|
403
|
+
// counts the cards `<memory>` block but not `<memory_spotlight>`, so the
|
|
404
|
+
// spotlight lands immediately after the cards rather than at the user
|
|
405
|
+
// tail). Cache-neutral: the block is strip-and-replaced from prior
|
|
406
|
+
// messages by block id every turn regardless of placement, so the
|
|
407
|
+
// frozen card prefix stays byte-stable and cached.
|
|
408
|
+
placement: "after-memory-prefix",
|
|
373
409
|
};
|
|
374
410
|
} catch (err) {
|
|
411
|
+
// Live-only injector: an infra failure must hard-fail the turn. The cards
|
|
412
|
+
// injector (ordered ahead of this one) normally throws first, so this
|
|
413
|
+
// path is defensive — it keeps the behavior correct if the cards injector
|
|
414
|
+
// is ever disabled or reordered.
|
|
415
|
+
if (err instanceof MemoryV3RetrievalUnavailableError) {
|
|
416
|
+
throw err;
|
|
417
|
+
}
|
|
375
418
|
log.warn(
|
|
376
419
|
{
|
|
377
420
|
err: err instanceof Error ? err.message : String(err),
|
|
@@ -383,7 +383,7 @@ export async function orchestrate(
|
|
|
383
383
|
* when the article or ordinal is not in the index (e.g. the dense store is
|
|
384
384
|
* ahead of the in-memory rebuild).
|
|
385
385
|
*/
|
|
386
|
-
function sectionByOrdinal(
|
|
386
|
+
export function sectionByOrdinal(
|
|
387
387
|
index: SectionIndex,
|
|
388
388
|
article: Slug,
|
|
389
389
|
ordinal: number,
|