@vellumai/assistant 0.8.12 → 0.9.0-staging.1
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/openapi.yaml +6935 -5545
- 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-dir-path-guard.test.ts +27 -3
- 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-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 +125 -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__/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__/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/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/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 +75 -0
- 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 +1 -1
- 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/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-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-stages/acl-enforcement.ts +26 -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 +122 -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/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 +48 -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
|
@@ -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,
|
|
@@ -30,15 +30,22 @@
|
|
|
30
30
|
* stable-prefix card and as a finder line (its current matched section);
|
|
31
31
|
* selections are deduped by slug.
|
|
32
32
|
*
|
|
33
|
-
* Failure handling
|
|
33
|
+
* Failure handling distinguishes a DELIBERATE empty selection from an
|
|
34
|
+
* INFRASTRUCTURE failure — the two are different outcomes, not the same one:
|
|
34
35
|
* - explicit `ids` → select exactly those candidates,
|
|
35
|
-
* - explicit empty `ids: []` → select none (deliberate abstention)
|
|
36
|
+
* - explicit empty `ids: []` → select none (deliberate abstention) — a
|
|
37
|
+
* normal, non-error result; the turn proceeds,
|
|
36
38
|
* - omitted `ids` → keep ALL candidates (the recall-safe "all of these are
|
|
37
39
|
* relevant" signal),
|
|
38
|
-
* -
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* - empty candidate pool → return none (nothing to select),
|
|
41
|
+
* - infrastructure failure (selector provider unavailable — e.g. a transient
|
|
42
|
+
* CES credential blip drops the API key — or no usable `tool_use` / schema
|
|
43
|
+
* mismatch surviving the short re-prompt retry) → throw
|
|
44
|
+
* {@link MemoryV3RetrievalUnavailableError}. There is NO deterministic-lane
|
|
45
|
+
* fallback: the LIVE injector propagates this to hard-fail the turn
|
|
46
|
+
* (retryable) rather than silently shipping it with no `<memory>` block,
|
|
47
|
+
* while the shadow/observation path swallows it and lets v2 retrieval serve
|
|
48
|
+
* the turn.
|
|
42
49
|
*/
|
|
43
50
|
|
|
44
51
|
import { z } from "zod";
|
|
@@ -60,6 +67,24 @@ import type { MemoryRoutingTurn, SelectedPage, Slug } from "./types.js";
|
|
|
60
67
|
|
|
61
68
|
const log = getLogger("memory-v3-pool-select");
|
|
62
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Thrown when the pool selector cannot produce a selection because of an
|
|
72
|
+
* INFRASTRUCTURE failure — its LLM provider is unavailable (e.g. a transient
|
|
73
|
+
* CES credential blip drops the API key), or no usable `tool_use` survives the
|
|
74
|
+
* re-prompt retry. Deliberately DISTINCT from a deliberate empty selection
|
|
75
|
+
* (`ids: []`) and an empty candidate pool, both of which return normally.
|
|
76
|
+
*
|
|
77
|
+
* The LIVE memory-v3 injector propagates this to hard-fail the turn (a clean,
|
|
78
|
+
* retryable failure) rather than silently shipping with no memory; the
|
|
79
|
+
* shadow/observation path catches and swallows it.
|
|
80
|
+
*/
|
|
81
|
+
export class MemoryV3RetrievalUnavailableError extends Error {
|
|
82
|
+
constructor(message: string) {
|
|
83
|
+
super(message);
|
|
84
|
+
this.name = "MemoryV3RetrievalUnavailableError";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
63
88
|
/** A dynamic-tail (finder) candidate: the slug plus the descriptor that
|
|
64
89
|
* justifies it — a matched section for a needle/dense hit, or a curated link
|
|
65
90
|
* description for an edge page. Rendered as a one-line snippet, prefixed
|
|
@@ -213,9 +238,11 @@ export async function selectPool(
|
|
|
213
238
|
if (!provider) {
|
|
214
239
|
log.warn(
|
|
215
240
|
{ candidateCount: ordered.length },
|
|
216
|
-
"pool selector provider unavailable
|
|
241
|
+
"pool selector provider unavailable — failing the turn rather than dropping memory",
|
|
242
|
+
);
|
|
243
|
+
throw new MemoryV3RetrievalUnavailableError(
|
|
244
|
+
"memory-v3 pool selector provider unavailable",
|
|
217
245
|
);
|
|
218
|
-
return [];
|
|
219
246
|
}
|
|
220
247
|
|
|
221
248
|
// Two content blocks: the stable prefix (cards) carries the cache
|
|
@@ -265,9 +292,11 @@ export async function selectPool(
|
|
|
265
292
|
if (parsed === null) {
|
|
266
293
|
log.warn(
|
|
267
294
|
{ candidateCount: ordered.length },
|
|
268
|
-
"pool selector could not obtain a selection after retries
|
|
295
|
+
"pool selector could not obtain a selection after retries — failing the turn rather than dropping memory",
|
|
296
|
+
);
|
|
297
|
+
throw new MemoryV3RetrievalUnavailableError(
|
|
298
|
+
"memory-v3 pool selector returned no usable selection after retries",
|
|
269
299
|
);
|
|
270
|
-
return [];
|
|
271
300
|
}
|
|
272
301
|
|
|
273
302
|
// Omitted `ids` is the recall-safe "keep all candidates" signal.
|