@vellumai/assistant 0.11.1 → 0.11.2-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/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createConversation,
|
|
5
|
+
type MessageRow,
|
|
6
|
+
} from "../../../../persistence/conversation-crud.js";
|
|
7
|
+
import { getDb } from "../../../../persistence/db-connection.js";
|
|
8
|
+
import { initializeDb } from "../../../../persistence/db-init.js";
|
|
9
|
+
import { messages } from "../../../../persistence/schema/index.js";
|
|
10
|
+
import {
|
|
11
|
+
hasQualifyingUserMessageAfter,
|
|
12
|
+
messagesHaveUserActivity,
|
|
13
|
+
} from "../memory-retrospective-accounting.js";
|
|
14
|
+
|
|
15
|
+
await initializeDb();
|
|
16
|
+
|
|
17
|
+
const CONV = "conv-accounting";
|
|
18
|
+
|
|
19
|
+
const TEXT = JSON.stringify([{ type: "text", text: "hello" }]);
|
|
20
|
+
const TOOL_RESULT_ONLY = JSON.stringify([
|
|
21
|
+
{ type: "tool_result", tool_use_id: "t1", content: "ok" },
|
|
22
|
+
]);
|
|
23
|
+
const MIXED = JSON.stringify([
|
|
24
|
+
{ type: "tool_result", tool_use_id: "t1", content: "ok" },
|
|
25
|
+
{ type: "text", text: "note" },
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
let seq = 0;
|
|
29
|
+
function insertRaw(opts: {
|
|
30
|
+
role: string;
|
|
31
|
+
content: string;
|
|
32
|
+
createdAt: number;
|
|
33
|
+
id?: string;
|
|
34
|
+
}): string {
|
|
35
|
+
const id = opts.id ?? `msg-${String(++seq).padStart(4, "0")}`;
|
|
36
|
+
getDb()
|
|
37
|
+
.insert(messages)
|
|
38
|
+
.values({
|
|
39
|
+
id,
|
|
40
|
+
conversationId: CONV,
|
|
41
|
+
role: opts.role,
|
|
42
|
+
content: opts.content,
|
|
43
|
+
createdAt: opts.createdAt,
|
|
44
|
+
metadata: null,
|
|
45
|
+
})
|
|
46
|
+
.run();
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("hasQualifyingUserMessageAfter", () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
const db = getDb();
|
|
53
|
+
db.run(`DELETE FROM messages`);
|
|
54
|
+
db.run(`DELETE FROM conversations`);
|
|
55
|
+
createConversation({ id: CONV });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("assistant-only tail does not qualify", () => {
|
|
59
|
+
insertRaw({ role: "assistant", content: TEXT, createdAt: 1_000 });
|
|
60
|
+
insertRaw({ role: "assistant", content: TEXT, createdAt: 2_000 });
|
|
61
|
+
|
|
62
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("tool_result-only user rows do not qualify", () => {
|
|
66
|
+
insertRaw({ role: "assistant", content: TEXT, createdAt: 1_000 });
|
|
67
|
+
insertRaw({ role: "user", content: TOOL_RESULT_ONLY, createdAt: 2_000 });
|
|
68
|
+
|
|
69
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("a user text row qualifies", () => {
|
|
73
|
+
insertRaw({ role: "user", content: TEXT, createdAt: 1_000 });
|
|
74
|
+
|
|
75
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("a mixed tool_result + text user row qualifies", () => {
|
|
79
|
+
insertRaw({ role: "user", content: MIXED, createdAt: 1_000 });
|
|
80
|
+
|
|
81
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("non-array user content qualifies (fails toward running)", () => {
|
|
85
|
+
// File-backed rows persist a `{ ref }` object; legacy rows persist a
|
|
86
|
+
// bare string. Neither shape can prove the row is a tool-result carrier.
|
|
87
|
+
insertRaw({
|
|
88
|
+
role: "user",
|
|
89
|
+
content: JSON.stringify({ ref: "deltas/abc" }),
|
|
90
|
+
createdAt: 1_000,
|
|
91
|
+
});
|
|
92
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
|
|
93
|
+
|
|
94
|
+
getDb().run(`DELETE FROM messages`);
|
|
95
|
+
insertRaw({ role: "user", content: "plain legacy text", createdAt: 1_000 });
|
|
96
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("an empty content array does not qualify", () => {
|
|
100
|
+
insertRaw({ role: "user", content: "[]", createdAt: 1_000 });
|
|
101
|
+
|
|
102
|
+
expect(hasQualifyingUserMessageAfter(CONV, null)).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("only rows after the cursor count", () => {
|
|
106
|
+
const cursor = insertRaw({ role: "user", content: TEXT, createdAt: 1_000 });
|
|
107
|
+
insertRaw({ role: "assistant", content: TEXT, createdAt: 2_000 });
|
|
108
|
+
|
|
109
|
+
expect(hasQualifyingUserMessageAfter(CONV, cursor)).toBe(false);
|
|
110
|
+
|
|
111
|
+
insertRaw({ role: "user", content: TEXT, createdAt: 3_000 });
|
|
112
|
+
expect(hasQualifyingUserMessageAfter(CONV, cursor)).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("the empty-string sentinel scans the whole conversation", () => {
|
|
116
|
+
insertRaw({ role: "user", content: TEXT, createdAt: 1_000 });
|
|
117
|
+
|
|
118
|
+
expect(hasQualifyingUserMessageAfter(CONV, "")).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("a vanished cursor reference means no new work", () => {
|
|
122
|
+
insertRaw({ role: "user", content: TEXT, createdAt: 1_000 });
|
|
123
|
+
|
|
124
|
+
expect(hasQualifyingUserMessageAfter(CONV, "msg-gone")).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("rows sharing the cursor's timestamp are split by the id tie-break", () => {
|
|
128
|
+
insertRaw({
|
|
129
|
+
role: "user",
|
|
130
|
+
content: TEXT,
|
|
131
|
+
createdAt: 1_000,
|
|
132
|
+
id: "msg-before",
|
|
133
|
+
});
|
|
134
|
+
insertRaw({
|
|
135
|
+
role: "user",
|
|
136
|
+
content: TOOL_RESULT_ONLY,
|
|
137
|
+
createdAt: 1_000,
|
|
138
|
+
id: "msg-cursor",
|
|
139
|
+
});
|
|
140
|
+
expect(hasQualifyingUserMessageAfter(CONV, "msg-cursor")).toBe(false);
|
|
141
|
+
|
|
142
|
+
insertRaw({
|
|
143
|
+
role: "user",
|
|
144
|
+
content: TEXT,
|
|
145
|
+
createdAt: 1_000,
|
|
146
|
+
id: "msg-later",
|
|
147
|
+
});
|
|
148
|
+
expect(hasQualifyingUserMessageAfter(CONV, "msg-cursor")).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("messagesHaveUserActivity", () => {
|
|
153
|
+
function row(
|
|
154
|
+
role: string,
|
|
155
|
+
content: Array<Record<string, unknown>>,
|
|
156
|
+
): Pick<MessageRow, "role" | "content"> {
|
|
157
|
+
return { role, content } as unknown as Pick<MessageRow, "role" | "content">;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
test("a user text row counts as user activity", () => {
|
|
161
|
+
expect(
|
|
162
|
+
messagesHaveUserActivity([
|
|
163
|
+
row("assistant", [{ type: "text", text: "recap" }]),
|
|
164
|
+
row("user", [{ type: "text", text: "hey" }]),
|
|
165
|
+
]),
|
|
166
|
+
).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("tool_result-only user rows do not count", () => {
|
|
170
|
+
expect(
|
|
171
|
+
messagesHaveUserActivity([
|
|
172
|
+
row("assistant", [{ type: "text", text: "recap" }]),
|
|
173
|
+
row("user", [
|
|
174
|
+
{ type: "tool_result", tool_use_id: "t1", content: "ok" },
|
|
175
|
+
]),
|
|
176
|
+
]),
|
|
177
|
+
).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("a mixed tool_result + text user row counts", () => {
|
|
181
|
+
expect(
|
|
182
|
+
messagesHaveUserActivity([
|
|
183
|
+
row("user", [
|
|
184
|
+
{ type: "tool_result", tool_use_id: "t1", content: "ok" },
|
|
185
|
+
{ type: "text", text: "note" },
|
|
186
|
+
]),
|
|
187
|
+
]),
|
|
188
|
+
).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("assistant rows never count, and an empty slice has no activity", () => {
|
|
192
|
+
expect(
|
|
193
|
+
messagesHaveUserActivity([
|
|
194
|
+
row("assistant", [{ type: "text", text: "hello" }]),
|
|
195
|
+
]),
|
|
196
|
+
).toBe(false);
|
|
197
|
+
expect(messagesHaveUserActivity([])).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -11,6 +11,19 @@ const upsertCalls: Array<{
|
|
|
11
11
|
payload: { conversationId: string };
|
|
12
12
|
runAfter: number;
|
|
13
13
|
}> = [];
|
|
14
|
+
let cfgRequireUserActivity = true;
|
|
15
|
+
let mockStateRow: {
|
|
16
|
+
conversationId: string;
|
|
17
|
+
lastProcessedMessageId: string;
|
|
18
|
+
lastRunAt: number;
|
|
19
|
+
rememberedLog: string[];
|
|
20
|
+
} | null = null;
|
|
21
|
+
let tailQualifies = true;
|
|
22
|
+
let gateProbeThrows = false;
|
|
23
|
+
let gateProbeCalls: Array<{
|
|
24
|
+
conversationId: string;
|
|
25
|
+
afterMessageId: string | null;
|
|
26
|
+
}> = [];
|
|
14
27
|
|
|
15
28
|
mock.module("../../../../persistence/conversation-crud.js", () => ({
|
|
16
29
|
getConversationSource: (_id: string) => sourceTag,
|
|
@@ -30,6 +43,29 @@ mock.module("../../../../persistence/jobs-store.js", () => ({
|
|
|
30
43
|
},
|
|
31
44
|
}));
|
|
32
45
|
|
|
46
|
+
mock.module("../../../../config/loader.js", () => ({
|
|
47
|
+
getConfig: () => ({
|
|
48
|
+
memory: { retrospective: { requireUserActivity: cfgRequireUserActivity } },
|
|
49
|
+
}),
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
mock.module("../memory-retrospective-state.js", () => ({
|
|
53
|
+
getRetrospectiveState: (_id: string) => mockStateRow,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
mock.module("../memory-retrospective-accounting.js", () => ({
|
|
57
|
+
hasQualifyingUserMessageAfter: (
|
|
58
|
+
conversationId: string,
|
|
59
|
+
afterMessageId: string | null,
|
|
60
|
+
) => {
|
|
61
|
+
gateProbeCalls.push({ conversationId, afterMessageId });
|
|
62
|
+
if (gateProbeThrows) {
|
|
63
|
+
throw new Error("messages table unavailable");
|
|
64
|
+
}
|
|
65
|
+
return tailQualifies;
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
|
|
33
69
|
import {
|
|
34
70
|
enqueueMemoryRetrospectiveIfEnabled,
|
|
35
71
|
enqueueMemoryRetrospectiveOnCompaction,
|
|
@@ -42,16 +78,22 @@ describe("enqueueMemoryRetrospectiveIfEnabled", () => {
|
|
|
42
78
|
convType = "standard";
|
|
43
79
|
convSource = "user";
|
|
44
80
|
upsertCalls.length = 0;
|
|
81
|
+
cfgRequireUserActivity = true;
|
|
82
|
+
mockStateRow = null;
|
|
83
|
+
tailQualifies = true;
|
|
84
|
+
gateProbeThrows = false;
|
|
85
|
+
gateProbeCalls = [];
|
|
45
86
|
});
|
|
46
87
|
|
|
47
88
|
test("standard source — interval trigger enqueues with runAfter ≈ now", () => {
|
|
48
89
|
const before = Date.now();
|
|
49
|
-
enqueueMemoryRetrospectiveIfEnabled({
|
|
90
|
+
const result = enqueueMemoryRetrospectiveIfEnabled({
|
|
50
91
|
conversationId: "c1",
|
|
51
92
|
trigger: "interval",
|
|
52
93
|
});
|
|
53
94
|
const after = Date.now();
|
|
54
95
|
|
|
96
|
+
expect(result).toBe(true);
|
|
55
97
|
expect(upsertCalls).toHaveLength(1);
|
|
56
98
|
const call = upsertCalls[0]!;
|
|
57
99
|
expect(call.payload).toEqual({ conversationId: "c1" });
|
|
@@ -59,6 +101,58 @@ describe("enqueueMemoryRetrospectiveIfEnabled", () => {
|
|
|
59
101
|
expect(call.runAfter).toBeLessThanOrEqual(after);
|
|
60
102
|
});
|
|
61
103
|
|
|
104
|
+
test("assistant-only tail — user-activity gate skips the enqueue", () => {
|
|
105
|
+
tailQualifies = false;
|
|
106
|
+
const result = enqueueMemoryRetrospectiveIfEnabled({
|
|
107
|
+
conversationId: "c1",
|
|
108
|
+
trigger: "interval",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result).toBe(false);
|
|
112
|
+
expect(upsertCalls).toHaveLength(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("requireUserActivity=false — assistant-only tail still enqueues", () => {
|
|
116
|
+
cfgRequireUserActivity = false;
|
|
117
|
+
tailQualifies = false;
|
|
118
|
+
const result = enqueueMemoryRetrospectiveIfEnabled({
|
|
119
|
+
conversationId: "c1",
|
|
120
|
+
trigger: "interval",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result).toBe(true);
|
|
124
|
+
expect(upsertCalls).toHaveLength(1);
|
|
125
|
+
expect(gateProbeCalls).toHaveLength(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("gate probes the tail from the state row's cursor", () => {
|
|
129
|
+
mockStateRow = {
|
|
130
|
+
conversationId: "c1",
|
|
131
|
+
lastProcessedMessageId: "m9",
|
|
132
|
+
lastRunAt: 1,
|
|
133
|
+
rememberedLog: [],
|
|
134
|
+
};
|
|
135
|
+
enqueueMemoryRetrospectiveIfEnabled({
|
|
136
|
+
conversationId: "c1",
|
|
137
|
+
trigger: "sweep",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(gateProbeCalls).toEqual([
|
|
141
|
+
{ conversationId: "c1", afterMessageId: "m9" },
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("an unevaluable gate fails open — the enqueue proceeds", () => {
|
|
146
|
+
gateProbeThrows = true;
|
|
147
|
+
const result = enqueueMemoryRetrospectiveIfEnabled({
|
|
148
|
+
conversationId: "c1",
|
|
149
|
+
trigger: "interval",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result).toBe(true);
|
|
153
|
+
expect(upsertCalls).toHaveLength(1);
|
|
154
|
+
});
|
|
155
|
+
|
|
62
156
|
test("compaction trigger applies the small debounce", () => {
|
|
63
157
|
const before = Date.now();
|
|
64
158
|
enqueueMemoryRetrospectiveIfEnabled({
|
|
@@ -135,6 +229,9 @@ describe("enqueueMemoryRetrospectiveOnCompaction", () => {
|
|
|
135
229
|
beforeEach(() => {
|
|
136
230
|
sourceTag = null;
|
|
137
231
|
upsertCalls.length = 0;
|
|
232
|
+
cfgRequireUserActivity = true;
|
|
233
|
+
tailQualifies = true;
|
|
234
|
+
gateProbeThrows = false;
|
|
138
235
|
});
|
|
139
236
|
|
|
140
237
|
test("untrusted trust class — no enqueue", () => {
|
|
@@ -28,7 +28,7 @@ let newMessages: Array<{
|
|
|
28
28
|
id: string;
|
|
29
29
|
createdAt: number;
|
|
30
30
|
role?: string;
|
|
31
|
-
content?: string
|
|
31
|
+
content?: string | Array<Record<string, unknown>>;
|
|
32
32
|
metadata?: string | null;
|
|
33
33
|
}> = [];
|
|
34
34
|
|
|
@@ -327,6 +327,7 @@ mock.module("../../../../config/memory-v3-gate.js", () => ({
|
|
|
327
327
|
import type { MemoryJob } from "../../../../persistence/jobs-store.js";
|
|
328
328
|
import {
|
|
329
329
|
memoryRetrospectiveJob,
|
|
330
|
+
runForkBasedRetrospective,
|
|
330
331
|
SOURCE_PROCESSING_REQUEUE_DELAY_MS,
|
|
331
332
|
} from "../memory-retrospective-job.js";
|
|
332
333
|
|
|
@@ -337,6 +338,7 @@ function makeConfig(
|
|
|
337
338
|
keepSupersededRuns?: boolean;
|
|
338
339
|
matchConversationProfile?: boolean;
|
|
339
340
|
promptPath?: string;
|
|
341
|
+
requireUserActivity?: boolean;
|
|
340
342
|
} = {},
|
|
341
343
|
): Parameters<typeof memoryRetrospectiveJob>[1] {
|
|
342
344
|
return {
|
|
@@ -346,6 +348,10 @@ function makeConfig(
|
|
|
346
348
|
keepSupersededRuns: overrides.keepSupersededRuns ?? false,
|
|
347
349
|
matchConversationProfile: overrides.matchConversationProfile ?? false,
|
|
348
350
|
promptPath: overrides.promptPath ?? null,
|
|
351
|
+
// Neutral in the stub (the shipped schema default is true): the
|
|
352
|
+
// default `newMessages` rows carry no roles, so the gate's own tests
|
|
353
|
+
// opt in explicitly with role-shaped slices.
|
|
354
|
+
requireUserActivity: overrides.requireUserActivity ?? false,
|
|
349
355
|
sweepIntervalMs: 8 * 60 * 60 * 1000,
|
|
350
356
|
sweepLookbackMs: 7 * 24 * 60 * 60 * 1000,
|
|
351
357
|
},
|
|
@@ -503,6 +509,104 @@ describe("memoryRetrospectiveJob", () => {
|
|
|
503
509
|
]);
|
|
504
510
|
});
|
|
505
511
|
|
|
512
|
+
test("assistant-only slice with the user-activity gate on: completed as a no-op, both pointers untouched", async () => {
|
|
513
|
+
newMessages = [
|
|
514
|
+
{
|
|
515
|
+
id: "m1",
|
|
516
|
+
createdAt: Date.parse("2026-05-11T10:00:00Z"),
|
|
517
|
+
role: "assistant",
|
|
518
|
+
content: [{ type: "text", text: "proactive recap" }],
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
id: "m2",
|
|
522
|
+
createdAt: Date.parse("2026-05-11T10:05:00Z"),
|
|
523
|
+
role: "user",
|
|
524
|
+
content: [{ type: "tool_result", tool_use_id: "t1", content: "ok" }],
|
|
525
|
+
},
|
|
526
|
+
];
|
|
527
|
+
|
|
528
|
+
const outcome = await memoryRetrospectiveJob(
|
|
529
|
+
makeJob(),
|
|
530
|
+
makeConfig({ requireUserActivity: true }),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
expect(outcome.kind).toBe("no_user_activity");
|
|
534
|
+
expect(stateUpserts).toHaveLength(0);
|
|
535
|
+
expect(lastRunAtBumps).toHaveLength(0);
|
|
536
|
+
expect(wakeCalls).toHaveLength(0);
|
|
537
|
+
expect(forkCalls).toHaveLength(0);
|
|
538
|
+
expect(
|
|
539
|
+
watchdogEvents.filter((e) => e.checkName === "memory_retrospective_run"),
|
|
540
|
+
).toEqual([
|
|
541
|
+
{
|
|
542
|
+
checkName: "memory_retrospective_run",
|
|
543
|
+
value: 1,
|
|
544
|
+
detail: { outcome: "no_user_activity" },
|
|
545
|
+
},
|
|
546
|
+
]);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test("a user text row in the slice passes the user-activity gate", async () => {
|
|
550
|
+
newMessages = [
|
|
551
|
+
{
|
|
552
|
+
id: "m1",
|
|
553
|
+
createdAt: Date.parse("2026-05-11T10:00:00Z"),
|
|
554
|
+
role: "assistant",
|
|
555
|
+
content: [{ type: "text", text: "proactive recap" }],
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
id: "m2",
|
|
559
|
+
createdAt: Date.parse("2026-05-11T10:05:00Z"),
|
|
560
|
+
role: "user",
|
|
561
|
+
content: [{ type: "text", text: "hey" }],
|
|
562
|
+
},
|
|
563
|
+
];
|
|
564
|
+
|
|
565
|
+
const outcome = await memoryRetrospectiveJob(
|
|
566
|
+
makeJob(),
|
|
567
|
+
makeConfig({ requireUserActivity: true }),
|
|
568
|
+
);
|
|
569
|
+
|
|
570
|
+
expect(outcome.kind).toBe("invoked");
|
|
571
|
+
expect(forkCalls).toHaveLength(1);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("requireUserActivity=false: an assistant-only slice still runs", async () => {
|
|
575
|
+
newMessages = [
|
|
576
|
+
{
|
|
577
|
+
id: "m1",
|
|
578
|
+
createdAt: Date.parse("2026-05-11T10:00:00Z"),
|
|
579
|
+
role: "assistant",
|
|
580
|
+
content: [{ type: "text", text: "proactive recap" }],
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
|
|
584
|
+
const outcome = await memoryRetrospectiveJob(
|
|
585
|
+
makeJob(),
|
|
586
|
+
makeConfig({ requireUserActivity: false }),
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
expect(outcome.kind).toBe("invoked");
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("manual runs bypass the user-activity gate: no opts means an assistant-only slice runs", async () => {
|
|
593
|
+
newMessages = [
|
|
594
|
+
{
|
|
595
|
+
id: "m1",
|
|
596
|
+
createdAt: Date.parse("2026-05-11T10:00:00Z"),
|
|
597
|
+
role: "assistant",
|
|
598
|
+
content: [{ type: "text", text: "proactive recap" }],
|
|
599
|
+
},
|
|
600
|
+
];
|
|
601
|
+
|
|
602
|
+
const outcome = await runForkBasedRetrospective(
|
|
603
|
+
"src-conv-1",
|
|
604
|
+
makeConfig({ requireUserActivity: true }),
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
expect(outcome.kind).toBe("invoked");
|
|
608
|
+
});
|
|
609
|
+
|
|
506
610
|
test("a slice whose only row is the retrospective's own skill card is no new work", async () => {
|
|
507
611
|
// The card is inserted AFTER the cursor the run just persisted, so it is
|
|
508
612
|
// always "after" lastProcessedMessageId — but it must never constitute
|
|
@@ -2,14 +2,18 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
// Record enqueues instead of writing job rows — the sweep's scan/gate decision
|
|
4
4
|
// is the unit under test, not the jobs store's coalescing. The enqueue's own
|
|
5
|
-
// recursion/low-yield guards are covered by
|
|
5
|
+
// recursion/low-yield/user-activity guards are covered by
|
|
6
|
+
// memory-retrospective-enqueue tests; `enqueueDeclines` scripts a declined
|
|
7
|
+
// conversation so the cap accounting can be asserted.
|
|
6
8
|
let enqueueCalls: Array<{ conversationId: string; trigger: string }> = [];
|
|
9
|
+
let enqueueDeclines = new Set<string>();
|
|
7
10
|
mock.module("../memory-retrospective-enqueue.js", () => ({
|
|
8
11
|
enqueueMemoryRetrospectiveIfEnabled: (args: {
|
|
9
12
|
conversationId: string;
|
|
10
13
|
trigger: string;
|
|
11
14
|
}) => {
|
|
12
15
|
enqueueCalls.push(args);
|
|
16
|
+
return !enqueueDeclines.has(args.conversationId);
|
|
13
17
|
},
|
|
14
18
|
}));
|
|
15
19
|
|
|
@@ -18,6 +22,7 @@ import { eq } from "drizzle-orm";
|
|
|
18
22
|
import type { AssistantConfig } from "../../../../config/types.js";
|
|
19
23
|
import { AUTO_ANALYSIS_SOURCE } from "../../../../persistence/auto-analysis-constants.js";
|
|
20
24
|
import { createConversation } from "../../../../persistence/conversation-crud.js";
|
|
25
|
+
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../../../persistence/conversation-types.js";
|
|
21
26
|
import {
|
|
22
27
|
getDb,
|
|
23
28
|
getMemorySqlite,
|
|
@@ -38,7 +43,6 @@ import {
|
|
|
38
43
|
runRetrospectiveSweep,
|
|
39
44
|
SWEEP_MAX_ENQUEUES_PER_PASS,
|
|
40
45
|
} from "../memory-retrospective-sweep.js";
|
|
41
|
-
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../substrate/constants.js";
|
|
42
46
|
|
|
43
47
|
await initializeDb();
|
|
44
48
|
|
|
@@ -104,6 +108,7 @@ describe("runRetrospectiveSweep", () => {
|
|
|
104
108
|
beforeEach(() => {
|
|
105
109
|
resetTables();
|
|
106
110
|
enqueueCalls = [];
|
|
111
|
+
enqueueDeclines = new Set();
|
|
107
112
|
});
|
|
108
113
|
|
|
109
114
|
test("never-run conversation with unprocessed messages is swept", async () => {
|
|
@@ -315,6 +320,23 @@ describe("runRetrospectiveSweep", () => {
|
|
|
315
320
|
expect(result).toEqual({ scanned: 1, enqueued: 1 });
|
|
316
321
|
});
|
|
317
322
|
|
|
323
|
+
test("an enqueue the funnel declines is not counted and does not consume the cap", async () => {
|
|
324
|
+
const declined = createConversation({ id: "conv-a" });
|
|
325
|
+
insertMessage(declined.id, { createdAt: 1_000 });
|
|
326
|
+
const accepted = createConversation({ id: "conv-b" });
|
|
327
|
+
insertMessage(accepted.id, { createdAt: 1_000 });
|
|
328
|
+
enqueueDeclines.add(declined.id);
|
|
329
|
+
|
|
330
|
+
const result = await runRetrospectiveSweep(makeConfig());
|
|
331
|
+
|
|
332
|
+
// Both conversations reach the funnel; only the accepted one counts.
|
|
333
|
+
expect(enqueueCalls.map((c) => c.conversationId)).toEqual([
|
|
334
|
+
declined.id,
|
|
335
|
+
accepted.id,
|
|
336
|
+
]);
|
|
337
|
+
expect(result).toEqual({ scanned: 2, enqueued: 1 });
|
|
338
|
+
});
|
|
339
|
+
|
|
318
340
|
test("enqueues clamp at the per-pass cap and defer the remainder", async () => {
|
|
319
341
|
for (let i = 0; i < SWEEP_MAX_ENQUEUES_PER_PASS + 5; i++) {
|
|
320
342
|
const conv = createConversation({
|