@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
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
// musings from the retrospective agent's own writes).
|
|
9
9
|
// - Source isn't a `scheduled` thread or a memory-consolidation background
|
|
10
10
|
// (low yield — see `isLowYieldRetrospectiveSource`).
|
|
11
|
+
// - The unprocessed tail contains user activity, when
|
|
12
|
+
// `memory.retrospective.requireUserActivity` is on. Assistant-only
|
|
13
|
+
// stretches (proactive sends composed by turns running elsewhere) carry
|
|
14
|
+
// no user turn, so their window anchor is undecidable and their content
|
|
15
|
+
// is a recap of work captured at its source; the gate defers them until
|
|
16
|
+
// real user activity arrives. Living here, every trigger kind funnels
|
|
17
|
+
// through one check.
|
|
11
18
|
//
|
|
12
19
|
// All trigger types funnel through `upsertMemoryRetrospectiveJob` which
|
|
13
20
|
// coalesces rapid enqueues into a single pending row per conversation.
|
|
@@ -23,10 +30,12 @@
|
|
|
23
30
|
// hooks ran (crash / IPC drop) and the conversation then went idle. See
|
|
24
31
|
// `memory-retrospective-sweep.ts`.
|
|
25
32
|
|
|
33
|
+
import { getConfig } from "../../../config/loader.js";
|
|
26
34
|
import {
|
|
27
35
|
getConversation,
|
|
28
36
|
getConversationSource,
|
|
29
37
|
} from "../../../persistence/conversation-crud.js";
|
|
38
|
+
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../../persistence/conversation-types.js";
|
|
30
39
|
import {
|
|
31
40
|
isMemoryEnabled,
|
|
32
41
|
upsertMemoryRetrospectiveJob,
|
|
@@ -34,8 +43,9 @@ import {
|
|
|
34
43
|
import { type TrustClass } from "../../../runtime/actor-trust-resolver.js";
|
|
35
44
|
import { resolveCapabilities } from "../../../runtime/capabilities.js";
|
|
36
45
|
import { getLogger } from "./logging.js";
|
|
46
|
+
import { hasQualifyingUserMessageAfter } from "./memory-retrospective-accounting.js";
|
|
37
47
|
import { isMemoryRetrospectiveSource } from "./memory-retrospective-constants.js";
|
|
38
|
-
import {
|
|
48
|
+
import { getRetrospectiveState } from "./memory-retrospective-state.js";
|
|
39
49
|
|
|
40
50
|
const log = getLogger("memory-retrospective-enqueue");
|
|
41
51
|
|
|
@@ -47,14 +57,19 @@ export type MemoryRetrospectiveTrigger =
|
|
|
47
57
|
|
|
48
58
|
const COMPACTION_DEBOUNCE_MS = 500;
|
|
49
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Returns true when a job row was upserted, false when a gate skipped the
|
|
62
|
+
* enqueue (or the upsert failed). The sweep counts only true returns toward
|
|
63
|
+
* its per-pass cap.
|
|
64
|
+
*/
|
|
50
65
|
export function enqueueMemoryRetrospectiveIfEnabled(args: {
|
|
51
66
|
conversationId: string;
|
|
52
67
|
trigger: MemoryRetrospectiveTrigger;
|
|
53
|
-
}):
|
|
68
|
+
}): boolean {
|
|
54
69
|
const { conversationId, trigger } = args;
|
|
55
70
|
|
|
56
71
|
if (!isMemoryEnabled()) {
|
|
57
|
-
return;
|
|
72
|
+
return false;
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
if (isMemoryRetrospectiveConversation(conversationId)) {
|
|
@@ -62,7 +77,7 @@ export function enqueueMemoryRetrospectiveIfEnabled(args: {
|
|
|
62
77
|
{ conversationId, trigger },
|
|
63
78
|
"Skipping memory-retrospective enqueue: source is a memory-retrospective conversation",
|
|
64
79
|
);
|
|
65
|
-
return;
|
|
80
|
+
return false;
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
if (isLowYieldRetrospectiveSource(conversationId)) {
|
|
@@ -70,7 +85,11 @@ export function enqueueMemoryRetrospectiveIfEnabled(args: {
|
|
|
70
85
|
{ conversationId, trigger },
|
|
71
86
|
"Skipping memory-retrospective enqueue: scheduled or consolidation source",
|
|
72
87
|
);
|
|
73
|
-
return;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!passesUserActivityGate(conversationId, trigger)) {
|
|
92
|
+
return false;
|
|
74
93
|
}
|
|
75
94
|
|
|
76
95
|
const runAfter =
|
|
@@ -83,6 +102,46 @@ export function enqueueMemoryRetrospectiveIfEnabled(args: {
|
|
|
83
102
|
{ err, conversationId, trigger },
|
|
84
103
|
"Failed to upsert memory-retrospective job",
|
|
85
104
|
);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The `memory.retrospective.requireUserActivity` gate: pass when the config
|
|
112
|
+
* is off or the unprocessed tail (everything after the conversation's
|
|
113
|
+
* `lastProcessedMessageId`) contains at least one user message carrying
|
|
114
|
+
* non-tool_result content. A gate that cannot be evaluated (config or DB
|
|
115
|
+
* unavailable) passes — an unevaluable gate must not silence retrospectives.
|
|
116
|
+
*/
|
|
117
|
+
function passesUserActivityGate(
|
|
118
|
+
conversationId: string,
|
|
119
|
+
trigger: MemoryRetrospectiveTrigger,
|
|
120
|
+
): boolean {
|
|
121
|
+
try {
|
|
122
|
+
if (!getConfig().memory.retrospective.requireUserActivity) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const state = getRetrospectiveState(conversationId);
|
|
126
|
+
if (
|
|
127
|
+
hasQualifyingUserMessageAfter(
|
|
128
|
+
conversationId,
|
|
129
|
+
state?.lastProcessedMessageId ?? null,
|
|
130
|
+
)
|
|
131
|
+
) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
log.debug(
|
|
135
|
+
{ conversationId, trigger },
|
|
136
|
+
"Skipping memory-retrospective enqueue: no user activity in the unprocessed tail",
|
|
137
|
+
);
|
|
138
|
+
return false;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
log.warn(
|
|
141
|
+
{ err, conversationId, trigger },
|
|
142
|
+
"User-activity gate check failed; enqueueing anyway",
|
|
143
|
+
);
|
|
144
|
+
return true;
|
|
86
145
|
}
|
|
87
146
|
}
|
|
88
147
|
|
|
@@ -80,7 +80,10 @@ import { wakeAgentForOpportunity } from "../../../runtime/agent-wake.js";
|
|
|
80
80
|
import { recordWatchdogEvent } from "../../../telemetry/watchdog-events-store.js";
|
|
81
81
|
import { findMostRecentRetrospectiveFor } from "./find-most-recent-retrospective-for.js";
|
|
82
82
|
import { getLogger } from "./logging.js";
|
|
83
|
-
import {
|
|
83
|
+
import {
|
|
84
|
+
getRetrospectiveMessagesAfter,
|
|
85
|
+
messagesHaveUserActivity,
|
|
86
|
+
} from "./memory-retrospective-accounting.js";
|
|
84
87
|
import {
|
|
85
88
|
MEMORY_RETROSPECTIVE_FORK_SOURCE,
|
|
86
89
|
MEMORY_RETROSPECTIVE_GROUP_ID,
|
|
@@ -125,6 +128,7 @@ const MEMORY_RETROSPECTIVE_RUN_CHECK_NAME = "memory_retrospective_run";
|
|
|
125
128
|
export type MemoryRetrospectiveOutcome =
|
|
126
129
|
| { kind: "disabled" }
|
|
127
130
|
| { kind: "no_new_messages" }
|
|
131
|
+
| { kind: "no_user_activity" }
|
|
128
132
|
| { kind: "source_dormant" }
|
|
129
133
|
| { kind: "source_processing" }
|
|
130
134
|
| { kind: "wake_failed"; reason?: string; conversationId?: string }
|
|
@@ -174,6 +178,7 @@ export async function memoryRetrospectiveJob(
|
|
|
174
178
|
try {
|
|
175
179
|
outcome = await runForkBasedRetrospective(sourceConversationId, config, {
|
|
176
180
|
enforceSweepLookback: true,
|
|
181
|
+
enforceUserActivityGate: true,
|
|
177
182
|
});
|
|
178
183
|
} catch (err) {
|
|
179
184
|
emitRunOutcome("error", err instanceof Error ? err.message : String(err));
|
|
@@ -209,6 +214,14 @@ export async function runForkBasedRetrospective(
|
|
|
209
214
|
* window.
|
|
210
215
|
*/
|
|
211
216
|
enforceSweepLookback?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Apply the `memory.retrospective.requireUserActivity` gate to the loaded
|
|
219
|
+
* slice. The queue handler passes this so rows that predate the enqueue
|
|
220
|
+
* gate (or that lost their user activity to a cursor race) complete as
|
|
221
|
+
* no-ops; the CLI's manual command omits it — an operator's explicit
|
|
222
|
+
* request overrides the gate.
|
|
223
|
+
*/
|
|
224
|
+
enforceUserActivityGate?: boolean;
|
|
212
225
|
},
|
|
213
226
|
): Promise<MemoryRetrospectiveOutcome> {
|
|
214
227
|
// Start stamp for the retrospective's end-to-end wall time, surfaced as
|
|
@@ -305,6 +318,23 @@ export async function runForkBasedRetrospective(
|
|
|
305
318
|
return { kind: "no_new_messages" };
|
|
306
319
|
}
|
|
307
320
|
|
|
321
|
+
// Execution-time twin of the enqueue funnel's user-activity gate. An
|
|
322
|
+
// assistant-only slice has no user turn to anchor the review window on and
|
|
323
|
+
// recaps work captured at its source, so it is deferred, not run: both
|
|
324
|
+
// state pointers stay untouched and the slice is reviewed by the first
|
|
325
|
+
// retrospective after real user activity arrives.
|
|
326
|
+
if (
|
|
327
|
+
opts?.enforceUserActivityGate === true &&
|
|
328
|
+
config.memory.retrospective.requireUserActivity &&
|
|
329
|
+
!messagesHaveUserActivity(newMessages)
|
|
330
|
+
) {
|
|
331
|
+
log.info(
|
|
332
|
+
{ sourceConversationId, newMessageCount: newMessages.length },
|
|
333
|
+
"memory-retrospective (fork): unprocessed tail has no user activity; skipping",
|
|
334
|
+
);
|
|
335
|
+
return { kind: "no_user_activity" };
|
|
336
|
+
}
|
|
337
|
+
|
|
308
338
|
const cutoffMessage = newMessages[newMessages.length - 1];
|
|
309
339
|
if (!cutoffMessage) {
|
|
310
340
|
return { kind: "no_new_messages" };
|
|
@@ -497,6 +527,11 @@ export async function runForkBasedRetrospective(
|
|
|
497
527
|
hintRole: "user",
|
|
498
528
|
skipHintInjection: true,
|
|
499
529
|
suppressAutoCompaction: true,
|
|
530
|
+
// Finalization consumes the window (cursor advance + prior-retro GC)
|
|
531
|
+
// on `invoked: true`, so an empty reply with no usable output must
|
|
532
|
+
// fail the wake and leave the window retryable instead of reading as
|
|
533
|
+
// a successful pass (LUM-3013).
|
|
534
|
+
requireUsableOutput: true,
|
|
500
535
|
// The fork's title already reads "(Retrospective)", so an empty-body
|
|
501
536
|
// "Conversation Woke" surface card on top of it would be noise. Suppress
|
|
502
537
|
// it — clients should display the fork as a normal background conv.
|
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
// - Enqueues route through `enqueueMemoryRetrospectiveIfEnabled`, whose
|
|
43
43
|
// `upsertMemoryRetrospectiveJob` coalesces against any already-pending job,
|
|
44
44
|
// so a conversation already queued by an event trigger is never
|
|
45
|
-
// double-processed.
|
|
45
|
+
// double-processed. An enqueue the funnel's gates decline (recursion,
|
|
46
|
+
// low-yield, user-activity) returns false and does not consume the
|
|
47
|
+
// per-pass cap.
|
|
46
48
|
// - Enqueues are capped at `SWEEP_MAX_ENQUEUES_PER_PASS` per pass. Inside
|
|
47
49
|
// the lookback window, organic stalled work is a handful of conversations;
|
|
48
50
|
// a pass wanting more signals an anomalous backlog. Each pass re-scans
|
|
@@ -65,6 +67,7 @@ import { and, asc, gt, ne, notInArray } from "drizzle-orm";
|
|
|
65
67
|
import type { AssistantConfig } from "../../../config/types.js";
|
|
66
68
|
import { AUTO_ANALYSIS_SOURCE } from "../../../persistence/auto-analysis-constants.js";
|
|
67
69
|
import { getConversationRecentProvenanceTrustClass } from "../../../persistence/conversation-crud.js";
|
|
70
|
+
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../../persistence/conversation-types.js";
|
|
68
71
|
import { getDb } from "../../../persistence/db-connection.js";
|
|
69
72
|
import type { MemoryJob } from "../../../persistence/jobs-store.js";
|
|
70
73
|
import {
|
|
@@ -77,7 +80,6 @@ import { countRetrospectiveMessagesAfter } from "./memory-retrospective-accounti
|
|
|
77
80
|
import { MEMORY_RETROSPECTIVE_SOURCES } from "./memory-retrospective-constants.js";
|
|
78
81
|
import { enqueueMemoryRetrospectiveIfEnabled } from "./memory-retrospective-enqueue.js";
|
|
79
82
|
import { getRetrospectiveState } from "./memory-retrospective-state.js";
|
|
80
|
-
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "./substrate/constants.js";
|
|
81
83
|
|
|
82
84
|
const log = getLogger("memory-retrospective-sweep");
|
|
83
85
|
|
|
@@ -277,7 +279,14 @@ export async function runRetrospectiveSweep(
|
|
|
277
279
|
continue;
|
|
278
280
|
}
|
|
279
281
|
|
|
280
|
-
|
|
282
|
+
if (
|
|
283
|
+
!enqueueMemoryRetrospectiveIfEnabled({
|
|
284
|
+
conversationId,
|
|
285
|
+
trigger: "sweep",
|
|
286
|
+
})
|
|
287
|
+
) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
281
290
|
enqueued += 1;
|
|
282
291
|
if (enqueued >= SWEEP_MAX_ENQUEUES_PER_PASS) {
|
|
283
292
|
log.warn(
|
|
@@ -35,6 +35,7 @@ import type { ToolDefinition } from "../../llm-helpers.js";
|
|
|
35
35
|
mock.module("../../substrate/skill-store.js", () => ({
|
|
36
36
|
SKILL_SLUG_PREFIX: "skills/",
|
|
37
37
|
listSkillEntries: () => [],
|
|
38
|
+
listAlwaysCandidateSkillSlugs: () => [],
|
|
38
39
|
seedV2SkillEntries: async () => undefined,
|
|
39
40
|
}));
|
|
40
41
|
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* slugs (`"skills/example-skill"`).
|
|
16
16
|
* - It swallows errors from the embedding backend — the function resolves
|
|
17
17
|
* and the cache is unchanged from prior state.
|
|
18
|
+
* - `listAlwaysCandidateSkillSlugs` serves pins (and their renderable cards)
|
|
19
|
+
* from the local catalog before any seed run completes, applies the same
|
|
20
|
+
* enablement/flag filtering the seeding path does, and yields to the seeded
|
|
21
|
+
* snapshot once one lands.
|
|
18
22
|
*
|
|
19
23
|
* Hermetic by design: the embedding backend, Qdrant module, and feature-flag
|
|
20
24
|
* resolver are module-mocked so the suite never reaches a real backend. One
|
|
@@ -55,6 +59,7 @@ interface BackfillCall {
|
|
|
55
59
|
|
|
56
60
|
interface TestState {
|
|
57
61
|
catalog: SkillSummary[] | null;
|
|
62
|
+
catalogThrows: Error | null;
|
|
58
63
|
resolved: ResolvedSkill[] | null;
|
|
59
64
|
fullCatalog: CatalogSkill[];
|
|
60
65
|
fullCatalogThrows: Error | null;
|
|
@@ -69,10 +74,12 @@ interface TestState {
|
|
|
69
74
|
backfillReturn: number;
|
|
70
75
|
backfillThrows: Error | null;
|
|
71
76
|
callSequence: Array<"upsert" | "prune" | "backfill">;
|
|
77
|
+
catalogLoadCount: number;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
const state: TestState = {
|
|
75
81
|
catalog: [],
|
|
82
|
+
catalogThrows: null,
|
|
76
83
|
resolved: [],
|
|
77
84
|
fullCatalog: [],
|
|
78
85
|
fullCatalogThrows: null,
|
|
@@ -87,10 +94,17 @@ const state: TestState = {
|
|
|
87
94
|
backfillReturn: 0,
|
|
88
95
|
backfillThrows: null,
|
|
89
96
|
callSequence: [],
|
|
97
|
+
catalogLoadCount: 0,
|
|
90
98
|
};
|
|
91
99
|
|
|
92
100
|
mock.module("../../../../../config/skills.js", () => ({
|
|
93
|
-
loadSkillCatalog: () =>
|
|
101
|
+
loadSkillCatalog: () => {
|
|
102
|
+
state.catalogLoadCount += 1;
|
|
103
|
+
if (state.catalogThrows) {
|
|
104
|
+
throw state.catalogThrows;
|
|
105
|
+
}
|
|
106
|
+
return state.catalog ?? [];
|
|
107
|
+
},
|
|
94
108
|
}));
|
|
95
109
|
|
|
96
110
|
mock.module("../../../../../config/skill-state.js", () => ({
|
|
@@ -187,6 +201,7 @@ mock.module("../../../../../skills/catalog-cache.js", () => ({
|
|
|
187
201
|
const {
|
|
188
202
|
seedV2SkillEntries,
|
|
189
203
|
getSkillCapability,
|
|
204
|
+
listAlwaysCandidateSkillSlugs,
|
|
190
205
|
listSkillEntries,
|
|
191
206
|
_resetSkillStoreForTests,
|
|
192
207
|
} = await import("../skill-store.js");
|
|
@@ -210,6 +225,7 @@ function makeSummary(overrides: Partial<SkillSummary> = {}): SkillSummary {
|
|
|
210
225
|
|
|
211
226
|
function resetState(): void {
|
|
212
227
|
state.catalog = [];
|
|
228
|
+
state.catalogThrows = null;
|
|
213
229
|
state.resolved = [];
|
|
214
230
|
state.fullCatalog = [];
|
|
215
231
|
state.fullCatalogThrows = null;
|
|
@@ -224,6 +240,7 @@ function resetState(): void {
|
|
|
224
240
|
state.backfillReturn = 0;
|
|
225
241
|
state.backfillThrows = null;
|
|
226
242
|
state.callSequence.length = 0;
|
|
243
|
+
state.catalogLoadCount = 0;
|
|
227
244
|
_resetSkillStoreForTests();
|
|
228
245
|
}
|
|
229
246
|
|
|
@@ -286,6 +303,34 @@ describe("seedV2SkillEntries", () => {
|
|
|
286
303
|
expect(state.upsertCalls[0].slug).toBe("skills/example-skill-a");
|
|
287
304
|
});
|
|
288
305
|
|
|
306
|
+
test("reports enabled always-candidate skills and excludes disabled ones", async () => {
|
|
307
|
+
const pinned = makeSummary({
|
|
308
|
+
id: "example-skill-a",
|
|
309
|
+
alwaysCandidate: true,
|
|
310
|
+
});
|
|
311
|
+
const pinnedButDisabled = makeSummary({
|
|
312
|
+
id: "example-skill-b",
|
|
313
|
+
alwaysCandidate: true,
|
|
314
|
+
});
|
|
315
|
+
const ordinary = makeSummary({ id: "example-skill-c" });
|
|
316
|
+
state.catalog = [pinned, pinnedButDisabled, ordinary];
|
|
317
|
+
state.resolved = [
|
|
318
|
+
{ summary: pinned, state: "enabled" },
|
|
319
|
+
{ summary: pinnedButDisabled, state: "disabled" },
|
|
320
|
+
{ summary: ordinary, state: "enabled" },
|
|
321
|
+
];
|
|
322
|
+
state.embedReturn = [
|
|
323
|
+
[0.1, 0.2, 0.3],
|
|
324
|
+
[0.4, 0.5, 0.6],
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
await seedV2SkillEntries();
|
|
328
|
+
|
|
329
|
+
expect(await listAlwaysCandidateSkillSlugs()).toEqual([
|
|
330
|
+
"skills/example-skill-a",
|
|
331
|
+
]);
|
|
332
|
+
});
|
|
333
|
+
|
|
289
334
|
test("does not re-seed an installed-but-disabled skill from the remote catalog", async () => {
|
|
290
335
|
// Regression: if `seenIds` is built only from enabled skills, a locally
|
|
291
336
|
// installed-but-disabled skill falls through to the catalog loop and gets
|
|
@@ -880,6 +925,155 @@ Write a local article draft.
|
|
|
880
925
|
});
|
|
881
926
|
});
|
|
882
927
|
|
|
928
|
+
describe("listAlwaysCandidateSkillSlugs — pre-seed catalog fallback", () => {
|
|
929
|
+
// Always-candidate membership is a static catalog fact. The boot seed is
|
|
930
|
+
// fire-and-forget and needs Qdrant plus a configured embedding backend, so on
|
|
931
|
+
// a freshly hatched assistant turn 1 lands before it finishes. Reading the
|
|
932
|
+
// catalog directly is what keeps the pin (and its card) alive on that turn.
|
|
933
|
+
|
|
934
|
+
test("reports always-candidate slugs before any seed run completes", async () => {
|
|
935
|
+
const pinned = makeSummary({
|
|
936
|
+
id: "example-skill-a",
|
|
937
|
+
displayName: "Skill A",
|
|
938
|
+
alwaysCandidate: true,
|
|
939
|
+
});
|
|
940
|
+
const ordinary = makeSummary({ id: "example-skill-b" });
|
|
941
|
+
state.catalog = [pinned, ordinary];
|
|
942
|
+
state.resolved = [
|
|
943
|
+
{ summary: pinned, state: "enabled" },
|
|
944
|
+
{ summary: ordinary, state: "enabled" },
|
|
945
|
+
];
|
|
946
|
+
|
|
947
|
+
expect(await listAlwaysCandidateSkillSlugs()).toEqual([
|
|
948
|
+
"skills/example-skill-a",
|
|
949
|
+
]);
|
|
950
|
+
// No seed ran, so nothing was embedded.
|
|
951
|
+
expect(state.upsertCalls).toHaveLength(0);
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
test("excludes disabled and flag-gated skills exactly as the seeding path does", async () => {
|
|
955
|
+
const pinned = makeSummary({
|
|
956
|
+
id: "example-skill-a",
|
|
957
|
+
alwaysCandidate: true,
|
|
958
|
+
});
|
|
959
|
+
const pinnedButDisabled = makeSummary({
|
|
960
|
+
id: "example-skill-b",
|
|
961
|
+
alwaysCandidate: true,
|
|
962
|
+
});
|
|
963
|
+
// Flag gating is resolved host-side: the gated skill is absent from
|
|
964
|
+
// `resolveSkillStates`, so it surfaces as `state: "unavailable"`.
|
|
965
|
+
const pinnedButGated = makeSummary({
|
|
966
|
+
id: "example-skill-c",
|
|
967
|
+
alwaysCandidate: true,
|
|
968
|
+
featureFlag: "off-flag",
|
|
969
|
+
});
|
|
970
|
+
state.catalog = [pinned, pinnedButDisabled, pinnedButGated];
|
|
971
|
+
state.resolved = [
|
|
972
|
+
{ summary: pinned, state: "enabled" },
|
|
973
|
+
{ summary: pinnedButDisabled, state: "disabled" },
|
|
974
|
+
];
|
|
975
|
+
|
|
976
|
+
expect(await listAlwaysCandidateSkillSlugs()).toEqual([
|
|
977
|
+
"skills/example-skill-a",
|
|
978
|
+
]);
|
|
979
|
+
expect(getSkillCapability("example-skill-b")).toBeNull();
|
|
980
|
+
expect(getSkillCapability("example-skill-c")).toBeNull();
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
test("renders the pinned skill's card content on the fallback path", async () => {
|
|
984
|
+
// The load-bearing half: `renderInjectionBlock` resolves each pinned slug
|
|
985
|
+
// through `getSkillCapability`, so a pin whose card does not resolve is
|
|
986
|
+
// silently dropped from the block.
|
|
987
|
+
const pinned = makeSummary({
|
|
988
|
+
id: "example-skill-a",
|
|
989
|
+
displayName: "Skill A",
|
|
990
|
+
description: "Draws inline visuals",
|
|
991
|
+
activationHints: ["user asks for a chart", "user asks for a diagram"],
|
|
992
|
+
avoidWhen: ["user wants a spreadsheet"],
|
|
993
|
+
alwaysCandidate: true,
|
|
994
|
+
});
|
|
995
|
+
state.catalog = [pinned];
|
|
996
|
+
state.resolved = [{ summary: pinned, state: "enabled" }];
|
|
997
|
+
|
|
998
|
+
const slugs = await listAlwaysCandidateSkillSlugs();
|
|
999
|
+
expect(slugs).toEqual(["skills/example-skill-a"]);
|
|
1000
|
+
|
|
1001
|
+
const entry = getSkillCapability(slugs[0]);
|
|
1002
|
+
expect(entry).not.toBeNull();
|
|
1003
|
+
expect(entry?.id).toBe("example-skill-a");
|
|
1004
|
+
expect(entry?.content).toContain('The "Skill A" skill');
|
|
1005
|
+
expect(entry?.content).toContain("Draws inline visuals");
|
|
1006
|
+
// The larger always-candidate budget switches the hints to a bulleted list,
|
|
1007
|
+
// proving the fallback renders through the same `buildSkillContent` budget
|
|
1008
|
+
// the seeding path uses.
|
|
1009
|
+
expect(entry?.content).toContain("Use when:\n- user asks for a chart");
|
|
1010
|
+
expect(entry?.content).toContain("Avoid when:\n- user wants a spreadsheet");
|
|
1011
|
+
// Bare-id lookup resolves the same entry.
|
|
1012
|
+
expect(getSkillCapability("example-skill-a")).toEqual(entry);
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
test("scans the catalog once and reuses the fallback across turns", async () => {
|
|
1016
|
+
const pinned = makeSummary({
|
|
1017
|
+
id: "example-skill-a",
|
|
1018
|
+
alwaysCandidate: true,
|
|
1019
|
+
});
|
|
1020
|
+
state.catalog = [pinned];
|
|
1021
|
+
state.resolved = [{ summary: pinned, state: "enabled" }];
|
|
1022
|
+
|
|
1023
|
+
await listAlwaysCandidateSkillSlugs();
|
|
1024
|
+
const afterFirst = state.catalogLoadCount;
|
|
1025
|
+
expect(afterFirst).toBeGreaterThan(0);
|
|
1026
|
+
|
|
1027
|
+
await listAlwaysCandidateSkillSlugs();
|
|
1028
|
+
await listAlwaysCandidateSkillSlugs();
|
|
1029
|
+
|
|
1030
|
+
expect(state.catalogLoadCount).toBe(afterFirst);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
test("the seeded snapshot replaces the fallback once a seed completes", async () => {
|
|
1034
|
+
const pinned = makeSummary({
|
|
1035
|
+
id: "example-skill-a",
|
|
1036
|
+
displayName: "Skill A",
|
|
1037
|
+
alwaysCandidate: true,
|
|
1038
|
+
});
|
|
1039
|
+
state.catalog = [pinned];
|
|
1040
|
+
state.resolved = [{ summary: pinned, state: "enabled" }];
|
|
1041
|
+
|
|
1042
|
+
expect(await listAlwaysCandidateSkillSlugs()).toEqual([
|
|
1043
|
+
"skills/example-skill-a",
|
|
1044
|
+
]);
|
|
1045
|
+
|
|
1046
|
+
// The user disables the skill, then a seed run lands: the seeded snapshot
|
|
1047
|
+
// is authoritative and the stale fallback must not resurrect the pin.
|
|
1048
|
+
state.resolved = [{ summary: pinned, state: "disabled" }];
|
|
1049
|
+
state.embedReturn = [];
|
|
1050
|
+
await seedV2SkillEntries();
|
|
1051
|
+
|
|
1052
|
+
expect(await listAlwaysCandidateSkillSlugs()).toEqual([]);
|
|
1053
|
+
expect(getSkillCapability("example-skill-a")).toBeNull();
|
|
1054
|
+
expect(listSkillEntries()).toEqual([]);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
test("a catalog read failure degrades to an empty pin set and is retried next turn", async () => {
|
|
1058
|
+
const pinned = makeSummary({
|
|
1059
|
+
id: "example-skill-a",
|
|
1060
|
+
alwaysCandidate: true,
|
|
1061
|
+
});
|
|
1062
|
+
state.catalog = [pinned];
|
|
1063
|
+
state.resolved = [{ summary: pinned, state: "enabled" }];
|
|
1064
|
+
state.catalogThrows = new Error("skills directory unreadable");
|
|
1065
|
+
|
|
1066
|
+
expect(await listAlwaysCandidateSkillSlugs()).toEqual([]);
|
|
1067
|
+
|
|
1068
|
+
// A failed build is not latched, so the next turn picks the catalog up.
|
|
1069
|
+
state.catalogThrows = null;
|
|
1070
|
+
|
|
1071
|
+
expect(await listAlwaysCandidateSkillSlugs()).toEqual([
|
|
1072
|
+
"skills/example-skill-a",
|
|
1073
|
+
]);
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
|
|
883
1077
|
describe("getSkillCapability", () => {
|
|
884
1078
|
test("returns null before any seed run", () => {
|
|
885
1079
|
expect(getSkillCapability("example-skill-a")).toBeNull();
|
|
@@ -83,6 +83,7 @@ import {
|
|
|
83
83
|
getMemoryCheckpoint,
|
|
84
84
|
setMemoryCheckpoint,
|
|
85
85
|
} from "../../../../persistence/checkpoints.js";
|
|
86
|
+
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../../../persistence/conversation-types.js";
|
|
86
87
|
import {
|
|
87
88
|
enqueueMemoryJob,
|
|
88
89
|
hasPendingJobOfType,
|
|
@@ -99,7 +100,6 @@ import {
|
|
|
99
100
|
releaseLock,
|
|
100
101
|
tryAcquireLock,
|
|
101
102
|
} from "./consolidation-lock.js";
|
|
102
|
-
import { MEMORY_V2_CONSOLIDATION_SOURCE } from "./constants.js";
|
|
103
103
|
import { getPageIndex, type PageParseFailure } from "./page-index.js";
|
|
104
104
|
import { resolveConsolidationPrompt } from "./prompts/consolidation.js";
|
|
105
105
|
import { resolveSubstrateTuning } from "./tuning.js";
|