@vellumai/assistant 0.11.8 → 0.11.9-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/ARCHITECTURE.md +2 -2
- package/Dockerfile +8 -48
- package/docker-entrypoint.sh +5 -1
- package/docker-kata-apt-env.sh +3 -0
- package/docker-kata-apt-shims.sh +127 -0
- package/docker-kata-apt-wrapper.sh +45 -0
- package/docker-kata-chroot-exec.sh +35 -0
- package/docker-kata-pip.sh +8 -2
- package/docs/architecture/memory.md +8 -4
- package/docs/guardian-request-flow.md +18 -14
- package/docs/trusted-contact-access.md +1 -7
- package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
- package/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/openapi.yaml +193 -4
- package/package.json +2 -2
- package/src/__tests__/access-request-card-view.test.ts +6 -5
- package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
- package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
- package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
- package/src/__tests__/attachments-store.test.ts +27 -0
- package/src/__tests__/attachments.test.ts +43 -0
- package/src/__tests__/canned-reply-release.test.ts +48 -5
- package/src/__tests__/channel-delivery-store.test.ts +129 -18
- package/src/__tests__/channel-reply-delivery.test.ts +553 -93
- package/src/__tests__/channel-retry-sweep.test.ts +199 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
- package/src/__tests__/conversation-error.test.ts +15 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
- package/src/__tests__/conversation-pairing.test.ts +141 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
- package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
- package/src/__tests__/conversation-sync-tags.test.ts +2 -37
- package/src/__tests__/credential-health-service.test.ts +98 -10
- package/src/__tests__/delete-propagation.test.ts +469 -0
- package/src/__tests__/dm-persistence.test.ts +16 -0
- package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
- package/src/__tests__/gemini-provider.test.ts +46 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
- package/src/__tests__/guardian-gateway-sim.ts +0 -23
- package/src/__tests__/guardian-question-mode.test.ts +108 -0
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
- package/src/__tests__/host-proxy-interface.test.ts +10 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
- package/src/__tests__/injector-v3-suppression.test.ts +43 -9
- package/src/__tests__/list-messages-page-latest.test.ts +127 -0
- package/src/__tests__/list-messages-system-card.test.ts +103 -0
- package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
- package/src/__tests__/notification-decision-fallback.test.ts +199 -77
- package/src/__tests__/notification-decision-strategy.test.ts +198 -213
- package/src/__tests__/notification-discord-adapter.test.ts +25 -0
- package/src/__tests__/notification-slack-adapter.test.ts +133 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
- package/src/__tests__/openai-provider.test.ts +5 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
- package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
- package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/process-message-display-content.test.ts +20 -9
- package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
- package/src/__tests__/provider-meta-persistence.test.ts +67 -0
- package/src/__tests__/reaction-persistence.test.ts +22 -198
- package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
- package/src/__tests__/skill-load-tool.test.ts +27 -0
- package/src/__tests__/skills.test.ts +34 -1
- package/src/__tests__/strip-memory-injections.test.ts +3 -4
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/thread-backfill.test.ts +5 -3
- package/src/__tests__/unified-turn-context-location.test.ts +76 -0
- package/src/__tests__/voice-session-bridge.test.ts +18 -0
- package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
- package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
- package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
- package/src/agent/attachments.ts +13 -2
- package/src/agent/loop.ts +3 -19
- package/src/api/README.md +9 -5
- package/src/api/index.ts +9 -8
- package/src/api/package.json +1 -0
- package/src/api/responses/conversation-message.ts +8 -0
- package/src/api/responses/home.ts +7 -18
- package/src/api/surfaces.ts +114 -7
- package/src/approvals/AGENTS.md +1 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
- package/src/channels/__tests__/types.test.ts +22 -1
- package/src/channels/gateway-guardian-requests.ts +0 -22
- package/src/channels/types.ts +8 -6
- package/src/cli/__tests__/catalog-search-help.test.ts +18 -0
- package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
- package/src/cli/commands/channels/index.help.ts +19 -6
- package/src/cli/commands/channels/index.ts +6 -2
- package/src/cli/commands/db/__tests__/status.test.ts +22 -0
- package/src/cli/commands/db/index.help.ts +1 -1
- package/src/cli/commands/db/status.ts +172 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
- package/src/cli/commands/platform/connect.ts +14 -5
- package/src/cli/commands/plugins.help.ts +6 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
- package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
- package/src/cli/lib/bundled-marketplace.json +14 -0
- package/src/cli/lib/install-from-github.ts +9 -5
- package/src/cli/lib/install-from-platform.ts +12 -1
- package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
- package/src/config/assistant-initiated-threads-gate.ts +50 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -3
- package/src/config/bundled-skills/schedule/SKILL.md +25 -11
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
- package/src/config/call-site-defaults.ts +4 -1
- package/src/config/feature-flag-registry.json +21 -4
- package/src/config/schemas/memory-v3.ts +4 -3
- package/src/context/strip-injections.ts +17 -56
- package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
- package/src/conversations/message-consolidation.ts +4 -3
- package/src/credential-health/credential-health-service.ts +130 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
- package/src/daemon/conversation-error.ts +2 -0
- package/src/daemon/conversation-messaging.ts +195 -47
- package/src/daemon/conversation-process.ts +20 -5
- package/src/daemon/conversation-runtime-assembly.ts +48 -39
- package/src/daemon/conversation-store.ts +159 -11
- package/src/daemon/conversation-tool-setup.ts +9 -2
- package/src/daemon/conversation.ts +290 -19
- package/src/daemon/dictation-text-processing.ts +2 -7
- package/src/daemon/handlers/config-channels.ts +33 -3
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/shared.ts +9 -0
- package/src/daemon/port-oversized-content.test.ts +117 -0
- package/src/daemon/port-oversized-content.ts +110 -0
- package/src/daemon/process-message.ts +87 -16
- package/src/daemon/reaction-record.test.ts +23 -11
- package/src/daemon/reaction-record.ts +6 -9
- package/src/documents/document-store.ts +1 -5
- package/src/home/conversation-starter-validation.ts +1 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
- package/src/live-voice/live-voice-photo.ts +544 -61
- package/src/live-voice/live-voice-session.ts +6 -2
- package/src/live-voice/protocol.ts +20 -1
- package/src/messaging/provider-message-metadata.ts +40 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
- package/src/messaging/providers/channel-transport.ts +28 -0
- package/src/messaging/providers/discord/send.ts +3 -2
- package/src/messaging/providers/slack/api.ts +11 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
- package/src/messaging/providers/slack/message-metadata.ts +141 -1
- package/src/messaging/providers/slack/send.test.ts +58 -0
- package/src/messaging/providers/slack/send.ts +4 -4
- package/src/messaging/providers/slack/transport.ts +28 -2
- package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
- package/src/messaging/providers/telegram-bot/send.ts +93 -0
- package/src/messaging/providers/telegram-bot/transport.ts +71 -0
- package/src/messaging/reaction-envelopes.test.ts +73 -0
- package/src/messaging/reaction-envelopes.ts +33 -5
- package/src/messaging/read-provider-metadata.ts +4 -3
- package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
- package/src/monitoring/recovery/db.ts +35 -0
- package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
- package/src/monitoring/recovery/run-recovery.ts +6 -2
- package/src/monitoring/recovery/stale-processing.ts +3 -20
- package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
- package/src/notifications/AGENTS.md +2 -2
- package/src/notifications/README.md +2 -2
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
- package/src/notifications/__tests__/broadcaster.test.ts +35 -4
- package/src/notifications/__tests__/notification-utils.test.ts +31 -0
- package/src/notifications/access-request-copy.ts +126 -114
- package/src/notifications/adapters/discord.ts +9 -5
- package/src/notifications/adapters/shared.ts +17 -4
- package/src/notifications/adapters/slack.ts +14 -4
- package/src/notifications/adapters/telegram.ts +8 -4
- package/src/notifications/approval-card-data.ts +5 -2
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/broadcaster.ts +57 -25
- package/src/notifications/conversation-pairing.ts +68 -2
- package/src/notifications/copy-composer.ts +13 -32
- package/src/notifications/decision-engine.ts +56 -237
- package/src/notifications/guardian-delivery-recorder.ts +5 -9
- package/src/notifications/guardian-feed-projection.ts +0 -15
- package/src/notifications/guardian-question-mode.ts +147 -6
- package/src/notifications/notification-utils.ts +117 -1
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -1
- package/src/persistence/conversation-crud.ts +93 -14
- package/src/persistence/conversation-queries.ts +144 -17
- package/src/persistence/conversation-types.ts +39 -5
- package/src/persistence/delivery-crud.ts +224 -28
- package/src/persistence/delivery-status.ts +21 -2
- package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
- package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
- package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
- package/src/persistence/schema/conversations.ts +71 -2
- package/src/persistence/schema-contract.test.ts +78 -0
- package/src/persistence/schema-contract.ts +96 -0
- package/src/persistence/steps.ts +4 -0
- package/src/platform/client.ts +55 -0
- package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
- package/src/plugins/defaults/injector-order.ts +2 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
- package/src/plugins/defaults/memory/injectors.ts +1 -1
- package/src/plugins/defaults/memory/memory-marker.ts +17 -7
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +17 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +7 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +22 -0
- package/src/plugins/defaults/memory/v3/injector.ts +14 -15
- package/src/plugins/defaults/memory/v3/prune.test.ts +20 -2
- package/src/plugins/defaults/memory/v3/render-injection.ts +10 -1
- package/src/plugins/defaults/memory/v3/types.ts +15 -4
- package/src/plugins/defaults/turn-context/injectors.ts +3 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
- package/src/plugins/mcp-servers.ts +14 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
- package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
- package/src/providers/anthropic/client.ts +21 -24
- package/src/providers/call-site-routing.ts +5 -4
- package/src/providers/connection-resolution.ts +29 -1
- package/src/providers/content-block-size.test.ts +82 -0
- package/src/providers/content-block-size.ts +99 -0
- package/src/providers/file-block-text.test.ts +64 -0
- package/src/providers/file-block-text.ts +28 -0
- package/src/providers/gemini/client.ts +15 -9
- package/src/providers/inference/auth.ts +6 -6
- package/src/providers/media-resolve.ts +14 -0
- package/src/providers/model-catalog.ts +47 -34
- package/src/providers/openai/chat-completions-provider.ts +9 -25
- package/src/providers/openai/responses-provider.ts +11 -18
- package/src/providers/registry.ts +10 -7
- package/src/providers/routing-identity.ts +2 -1
- package/src/providers/types.ts +1 -4
- package/src/providers/vellum-model-routing.ts +3 -2
- package/src/runtime/AGENTS.md +40 -15
- package/src/runtime/approval-message-composer.ts +1 -6
- package/src/runtime/assistant-event-hub.ts +2 -39
- package/src/runtime/channel-approval-types.ts +1 -5
- package/src/runtime/channel-reply-delivery.ts +182 -115
- package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
- package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
- package/src/runtime/channel-retry-sweep.ts +102 -1
- package/src/runtime/finalize-event-delivery.ts +4 -4
- package/src/runtime/guardian-action-message-composer.ts +1 -4
- package/src/runtime/guardian-reply-router.ts +4 -75
- package/src/runtime/http-router.ts +1 -5
- package/src/runtime/question-request-guardian-bridge.ts +2 -3
- package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
- package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
- package/src/runtime/routes/acp-routes.ts +1 -1
- package/src/runtime/routes/canned-reply-release.ts +19 -9
- package/src/runtime/routes/channel-route-shared.ts +0 -39
- package/src/runtime/routes/channel-verification-routes.ts +4 -1
- package/src/runtime/routes/conversation-list-routes.ts +44 -3
- package/src/runtime/routes/conversation-management-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +138 -59
- package/src/runtime/routes/diagnostics-routes.ts +111 -60
- package/src/runtime/routes/guardian-action-routes.ts +10 -14
- package/src/runtime/routes/guardian-approval-interception.ts +0 -5
- package/src/runtime/routes/inbound-message-handler.ts +170 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/platform-routes.ts +99 -10
- package/src/runtime/routes/sight-frame-routes.ts +136 -0
- package/src/runtime/sync/resource-sync-events.ts +14 -37
- package/src/runtime/sync/sync-publisher.test.ts +5 -4
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/client-os.ts +11 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +4 -3
- package/src/tools/watch/watch-retro-report.ts +205 -0
- package/src/watch/__tests__/watch-retro.test.ts +268 -40
- package/src/watch/watch-retro.ts +190 -77
- package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
- package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +4 -0
- package/docker-kata-pip-chroot.sh +0 -22
- package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
- package/src/api/events/conversation-list-invalidated.ts +0 -38
|
@@ -53,6 +53,7 @@ import type { GraphMemoryResult } from "../graph/conversation-graph-memory.js";
|
|
|
53
53
|
import { recordMemoryRecallLog } from "../memory-recall-log-store.js";
|
|
54
54
|
import { stripTailInjectionsForReinjection } from "../tail-reinjection-strip.js";
|
|
55
55
|
import { MEMORY_V3_INJECTED_BLOCK_METADATA_KEY } from "../v3/ever-injected-store.js";
|
|
56
|
+
import { MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY } from "../v3/types.js";
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* Whether to run legacy graph-memory retrieval this turn. It gates BOTH
|
|
@@ -220,6 +221,10 @@ async function recordRecallSideEffects(
|
|
|
220
221
|
* - `blocks.memoryV3InjectedBlock` (the frozen net-new card block, unwrapped)
|
|
221
222
|
* persists under `MEMORY_V3_INJECTED_BLOCK_METADATA_KEY`; `loadFromDb`
|
|
222
223
|
* re-wraps and splices it on load, freezing the cards into history.
|
|
224
|
+
* - `blocks.memoryV3SpotlightBlock` (the wrapped `<memory_spotlight>` that
|
|
225
|
+
* was sent) persists under `MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY`;
|
|
226
|
+
* `loadFromDb` splices it back onto the same row so historical turns
|
|
227
|
+
* keep the spotlight they were sent with.
|
|
223
228
|
*/
|
|
224
229
|
async function persistInjectionBlocks(
|
|
225
230
|
blocks: RuntimeInjectionResult["blocks"],
|
|
@@ -235,6 +240,7 @@ async function persistInjectionBlocks(
|
|
|
235
240
|
!blocks.pkbContextBlock &&
|
|
236
241
|
!blocks.memoryV2StaticBlock &&
|
|
237
242
|
!blocks.memoryV3InjectedBlock &&
|
|
243
|
+
!blocks.memoryV3SpotlightBlock &&
|
|
238
244
|
!blocks.backgroundTurnBlock &&
|
|
239
245
|
!blocks.channelCapabilitiesBlock &&
|
|
240
246
|
!blocks.nonInteractiveContextBlock &&
|
|
@@ -255,6 +261,10 @@ async function persistInjectionBlocks(
|
|
|
255
261
|
metadataUpdates[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY] =
|
|
256
262
|
blocks.memoryV3InjectedBlock;
|
|
257
263
|
}
|
|
264
|
+
if (blocks.memoryV3SpotlightBlock) {
|
|
265
|
+
metadataUpdates[MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY] =
|
|
266
|
+
blocks.memoryV3SpotlightBlock;
|
|
267
|
+
}
|
|
258
268
|
if (blocks.unifiedTurnContext) {
|
|
259
269
|
metadataUpdates.turnContextBlock = blocks.unifiedTurnContext;
|
|
260
270
|
}
|
|
@@ -428,7 +428,7 @@ export const memoryInjectors: Injector[] = [
|
|
|
428
428
|
// `memory-v2-static` is FROZEN: `daemon/conversation-runtime-assembly.ts`
|
|
429
429
|
// switches on it to capture the block into persisted message metadata.
|
|
430
430
|
memoryV2StaticInjector,
|
|
431
|
-
// V3
|
|
431
|
+
// V3: delete with v3. The frozen net-new card block and its per-turn
|
|
432
432
|
// spotlight companion; both self-gate on `memory.v3.live`.
|
|
433
433
|
memoryV3Injector,
|
|
434
434
|
memoryV3SpotlightInjector,
|
|
@@ -30,13 +30,11 @@ export function unwrapMemoryBlock(block: string): string {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* The memory-v3
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* `
|
|
37
|
-
*
|
|
38
|
-
* prefix/suffix pair, so the producer wrapper lives here beside the `<memory>`
|
|
39
|
-
* marker it parallels.
|
|
33
|
+
* The memory-v3 per-turn spotlight wrapper. Each turn's `<memory_spotlight>`
|
|
34
|
+
* stays on the user message that was sent, matching frozen `<memory>` cards.
|
|
35
|
+
* Compaction still strips these blocks via `RUNTIME_INJECTION_PREFIXES`, and
|
|
36
|
+
* `stripSpotlightInjections` is the full-history leftover cleanup used there.
|
|
37
|
+
* The producer wrapper lives here beside the `<memory>` marker it parallels.
|
|
40
38
|
*/
|
|
41
39
|
export const MEMORY_SPOTLIGHT_PREFIX = "<memory_spotlight>\n";
|
|
42
40
|
export const MEMORY_SPOTLIGHT_SUFFIX = "\n</memory_spotlight>";
|
|
@@ -44,3 +42,15 @@ export const MEMORY_SPOTLIGHT_SUFFIX = "\n</memory_spotlight>";
|
|
|
44
42
|
export function wrapMemorySpotlightBlock(text: string): string {
|
|
45
43
|
return `${MEMORY_SPOTLIGHT_PREFIX}${text}${MEMORY_SPOTLIGHT_SUFFIX}`;
|
|
46
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether `text` is a complete `<memory_spotlight>…</memory_spotlight>`
|
|
48
|
+
* wrapper. Requires both ends so user-authored text that merely opens with
|
|
49
|
+
* the tag is not treated as an injected spotlight.
|
|
50
|
+
*/
|
|
51
|
+
export function isMemorySpotlightText(text: string): boolean {
|
|
52
|
+
return (
|
|
53
|
+
text.startsWith(MEMORY_SPOTLIGHT_PREFIX) &&
|
|
54
|
+
text.endsWith(MEMORY_SPOTLIGHT_SUFFIX)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -103,12 +103,8 @@ function loadDefaultDisabledAssistantFlagKeys(): string[] {
|
|
|
103
103
|
.sort();
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
function escapeRegExp(value: string): string {
|
|
107
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
106
|
function featureFlagKeyPattern(key: string): RegExp {
|
|
111
|
-
const escaped =
|
|
107
|
+
const escaped = RegExp.escape(key);
|
|
112
108
|
if (key.includes("-")) {
|
|
113
109
|
return new RegExp(`(?:^|[^a-z0-9-])${escaped}(?:$|[^a-z0-9-])`, "i");
|
|
114
110
|
}
|
|
@@ -119,3 +119,20 @@ describe("augmentMcpSetupDescription", () => {
|
|
|
119
119
|
expect(out.id).toBe("mcp-setup");
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
|
+
|
|
123
|
+
describe("SKILLS_INJECTION_CATALOG_HINT", () => {
|
|
124
|
+
test("points a missing product at plugin and skill search", async () => {
|
|
125
|
+
const { SKILLS_INJECTION_CATALOG_HINT } = await import(
|
|
126
|
+
"../skill-content.js"
|
|
127
|
+
);
|
|
128
|
+
expect(SKILLS_INJECTION_CATALOG_HINT).toContain(
|
|
129
|
+
"assistant plugins search <name>",
|
|
130
|
+
);
|
|
131
|
+
expect(SKILLS_INJECTION_CATALOG_HINT).toContain(
|
|
132
|
+
"assistant skills search <name>",
|
|
133
|
+
);
|
|
134
|
+
expect(SKILLS_INJECTION_CATALOG_HINT.toLowerCase()).toContain(
|
|
135
|
+
"currently in the workspace",
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -29,6 +29,13 @@ export const ALWAYS_CANDIDATE_CARD_CHARS = 900;
|
|
|
29
29
|
*/
|
|
30
30
|
export const DEFAULT_CARD_CHARS = 500;
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* One-shot line under injected skill cards. The cards list workspace skills
|
|
34
|
+
* only, so a missing product is not evidence it is unsupported.
|
|
35
|
+
*/
|
|
36
|
+
export const SKILLS_INJECTION_CATALOG_HINT =
|
|
37
|
+
"Injected skills are only ones currently in the workspace. Run `assistant plugins search <name>` and `assistant skills search <name>` before concluding a given integration or skill is unsupported";
|
|
38
|
+
|
|
32
39
|
/**
|
|
33
40
|
* Render the prose-style capability statement embedded into the unified
|
|
34
41
|
* `memory_v2_concept_pages` Qdrant collection (under the `skills/<id>` slug
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
* → selectPool (real two-segment render + cache breakpoint; the PROVIDER
|
|
13
13
|
* is stubbed to return deterministic ids per scripted turn)
|
|
14
14
|
* → the real injectors (`memoryV3Injector` net-new cards + recordInjected
|
|
15
|
-
* + schedulePruneValve; `memoryV3SpotlightInjector`
|
|
16
|
-
* → simulated runtime assembly (splice the card block
|
|
17
|
-
* user message;
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* + schedulePruneValve; `memoryV3SpotlightInjector` per-turn window)
|
|
16
|
+
* → simulated runtime assembly (splice the card block and the
|
|
17
|
+
* spotlight onto the current user message; do not rewrite historical
|
|
18
|
+
* spotlights) and metadata persistence (the user-prompt-submit
|
|
19
|
+
* hook's `memoryV3InjectedBlock` and `memoryV3SpotlightBlock` writes)
|
|
20
20
|
* → the real prune valve against the live history (conversation-registry
|
|
21
21
|
* stubbed to the simulated message arrays)
|
|
22
22
|
* → rehydration from the temp DB (mirroring `daemon/conversation.ts`'s
|
|
@@ -57,7 +57,6 @@ import type {
|
|
|
57
57
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
58
58
|
|
|
59
59
|
import { setConfig } from "../../../../../__tests__/helpers/set-config.js";
|
|
60
|
-
import { stripSpotlightInjections } from "../../../../../context/strip-injections.js";
|
|
61
60
|
import { ensureMemoryV3SelectionsSchema } from "../../../../../persistence/migrations/338-move-memory-v3-selections-to-memory-db.js";
|
|
62
61
|
import { ensureMemoryV3EverInjectedSchema } from "../../../../../persistence/migrations/345-move-memory-v3-ever-injected-to-memory-db.js";
|
|
63
62
|
import * as schema from "../../../../../persistence/schema/index.js";
|
|
@@ -71,6 +70,7 @@ import { buildSectionNeedle } from "../section-needle.js";
|
|
|
71
70
|
import { buildSectionIndex } from "../sections.js";
|
|
72
71
|
import {
|
|
73
72
|
MEMORY_V3_COMMIT_META_KEY,
|
|
73
|
+
MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY,
|
|
74
74
|
type SectionIndex,
|
|
75
75
|
type Slug,
|
|
76
76
|
} from "../types.js";
|
|
@@ -561,7 +561,7 @@ interface TurnRecord {
|
|
|
561
561
|
spotlightPlacement: string;
|
|
562
562
|
residentBytes: number;
|
|
563
563
|
prunedSlugs: Set<string>;
|
|
564
|
-
/** JSON of the
|
|
564
|
+
/** JSON of the live history after the turn (cards + spotlight persist). */
|
|
565
565
|
snapshot: string;
|
|
566
566
|
}
|
|
567
567
|
|
|
@@ -583,7 +583,7 @@ function insertMessageRow(
|
|
|
583
583
|
}
|
|
584
584
|
|
|
585
585
|
function persistentView(history: Message[]): string {
|
|
586
|
-
return JSON.stringify(
|
|
586
|
+
return JSON.stringify(history);
|
|
587
587
|
}
|
|
588
588
|
|
|
589
589
|
async function runTurn(
|
|
@@ -646,29 +646,34 @@ async function runTurn(
|
|
|
646
646
|
if (cards.text.length > 0) {
|
|
647
647
|
const tail = history[history.length - 1]!;
|
|
648
648
|
tail.content = [{ type: "text", text: cards.text }, ...tail.content];
|
|
649
|
-
testSqlite
|
|
650
|
-
.query(/*sql*/ `UPDATE messages SET metadata = ? WHERE id = ?`)
|
|
651
|
-
.run(
|
|
652
|
-
JSON.stringify({
|
|
653
|
-
[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY]: unwrapMemoryBlock(
|
|
654
|
-
cards.text,
|
|
655
|
-
),
|
|
656
|
-
}),
|
|
657
|
-
userRowId,
|
|
658
|
-
);
|
|
659
649
|
}
|
|
660
650
|
|
|
661
|
-
// Spotlight
|
|
662
|
-
//
|
|
663
|
-
// (after-memory-prefix); this sim appends to the tail because only the
|
|
664
|
-
// block's presence, content, placement value, and strip-and-replace are
|
|
665
|
-
// asserted here — not its exact position within the message.
|
|
651
|
+
// Spotlight splices after the frozen cards (after-memory-prefix). Historical
|
|
652
|
+
// user messages keep the spotlight they already carry.
|
|
666
653
|
const spotlight = await memoryV3SpotlightInjector.produce(ctx);
|
|
667
|
-
|
|
668
|
-
history.splice(0, history.length, ...stripped);
|
|
669
|
-
if (spotlight) {
|
|
654
|
+
if (spotlight && spotlight.text.length > 0) {
|
|
670
655
|
const tail = history[history.length - 1]!;
|
|
671
|
-
|
|
656
|
+
const prefixCount = cards.text.length > 0 ? 1 : 0;
|
|
657
|
+
tail.content = [
|
|
658
|
+
...tail.content.slice(0, prefixCount),
|
|
659
|
+
{ type: "text", text: spotlight.text },
|
|
660
|
+
...tail.content.slice(prefixCount),
|
|
661
|
+
];
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const metadata: Record<string, string> = {};
|
|
665
|
+
if (cards.text.length > 0) {
|
|
666
|
+
metadata[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY] = unwrapMemoryBlock(
|
|
667
|
+
cards.text,
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
if (spotlight?.text) {
|
|
671
|
+
metadata[MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY] = spotlight.text;
|
|
672
|
+
}
|
|
673
|
+
if (Object.keys(metadata).length > 0) {
|
|
674
|
+
testSqlite
|
|
675
|
+
.query(/*sql*/ `UPDATE messages SET metadata = ? WHERE id = ?`)
|
|
676
|
+
.run(JSON.stringify(metadata), userRowId);
|
|
672
677
|
}
|
|
673
678
|
|
|
674
679
|
const replyContent: ContentBlock[] = [
|
|
@@ -726,6 +731,10 @@ function rehydrateFromDb(convId: string): Message[] {
|
|
|
726
731
|
let content = JSON.parse(row.content) as ContentBlock[];
|
|
727
732
|
if (row.role === "user" && row.metadata) {
|
|
728
733
|
const meta = JSON.parse(row.metadata) as Record<string, unknown>;
|
|
734
|
+
const spotlight = meta[MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY];
|
|
735
|
+
if (typeof spotlight === "string") {
|
|
736
|
+
content = [{ type: "text", text: spotlight }, ...content];
|
|
737
|
+
}
|
|
729
738
|
const block = meta[MEMORY_V3_INJECTED_BLOCK_METADATA_KEY];
|
|
730
739
|
if (typeof block === "string") {
|
|
731
740
|
const resident = filterPrunedCardSections(
|
|
@@ -1014,7 +1023,7 @@ describe("memory-v3 carry integration — cache contract", () => {
|
|
|
1014
1023
|
});
|
|
1015
1024
|
|
|
1016
1025
|
describe("memory-v3 carry integration — spotlight contract", () => {
|
|
1017
|
-
test("spotlight is
|
|
1026
|
+
test("spotlight is produced every turn, spliced after cards, bounded by n × (window + 1)", () => {
|
|
1018
1027
|
for (const record of records) {
|
|
1019
1028
|
expect(record.spotlightText.startsWith("<memory_spotlight>\n")).toBe(
|
|
1020
1029
|
true,
|
|
@@ -1040,12 +1049,9 @@ describe("memory-v3 carry integration — spotlight contract", () => {
|
|
|
1040
1049
|
expect(records[5]!.spotlightText).not.toContain("page-a.md");
|
|
1041
1050
|
});
|
|
1042
1051
|
|
|
1043
|
-
test("
|
|
1052
|
+
test("each turn persists its spotlight on that user message and keeps historical copies", () => {
|
|
1044
1053
|
for (const record of records) {
|
|
1045
1054
|
expect(record.blockText).not.toContain("<memory_spotlight>");
|
|
1046
|
-
// The persistent-layer snapshot is spotlight-stripped by construction;
|
|
1047
|
-
// the raw live history must carry exactly ONE spotlight block (the
|
|
1048
|
-
// current turn's).
|
|
1049
1055
|
}
|
|
1050
1056
|
const metadataRows = testSqlite
|
|
1051
1057
|
.query(
|
|
@@ -1056,9 +1062,10 @@ describe("memory-v3 carry integration — spotlight contract", () => {
|
|
|
1056
1062
|
)
|
|
1057
1063
|
.all(CONV) as Array<{ metadata: string }>;
|
|
1058
1064
|
expect(metadataRows.length).toBeGreaterThan(0);
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1065
|
+
const persistedSpotlights = metadataRows.filter((row) =>
|
|
1066
|
+
row.metadata.includes(MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY),
|
|
1067
|
+
);
|
|
1068
|
+
expect(persistedSpotlights.length).toBeGreaterThan(1);
|
|
1062
1069
|
const liveSpotlights = histories
|
|
1063
1070
|
.get(CONV)!
|
|
1064
1071
|
.flatMap((m) => m.content)
|
|
@@ -1066,7 +1073,7 @@ describe("memory-v3 carry integration — spotlight contract", () => {
|
|
|
1066
1073
|
(b): b is { type: "text"; text: string } =>
|
|
1067
1074
|
b.type === "text" && b.text.startsWith("<memory_spotlight>\n"),
|
|
1068
1075
|
);
|
|
1069
|
-
expect(liveSpotlights).
|
|
1076
|
+
expect(liveSpotlights.length).toBeGreaterThan(1);
|
|
1070
1077
|
});
|
|
1071
1078
|
});
|
|
1072
1079
|
|
|
@@ -1106,15 +1113,21 @@ describe("memory-v3 carry integration — restart contract", () => {
|
|
|
1106
1113
|
|
|
1107
1114
|
test("pruned sections are absent from the rehydrated history (metadata stays intact)", () => {
|
|
1108
1115
|
const rehydrated = JSON.parse(restartRehydratedJson) as Message[];
|
|
1109
|
-
const
|
|
1116
|
+
const cardText = rehydrated
|
|
1110
1117
|
.flatMap((m) => m.content)
|
|
1111
1118
|
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
1119
|
+
.filter(
|
|
1120
|
+
(b) =>
|
|
1121
|
+
b.text.startsWith("<memory>\n") && b.text.endsWith("\n</memory>"),
|
|
1122
|
+
)
|
|
1112
1123
|
.map((b) => b.text)
|
|
1113
1124
|
.join("\n");
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
expect(
|
|
1125
|
+
// Prune filters card sections on rehydrate. Historical spotlights stay
|
|
1126
|
+
// as sent, so a pruned slug can still appear under `##` in those blocks.
|
|
1127
|
+
expect(cardText).not.toContain("# memory/concepts/page-a.md");
|
|
1128
|
+
expect(cardText).not.toContain("# memory/concepts/page-b.md");
|
|
1129
|
+
// Resident cards survive the round trip.
|
|
1130
|
+
expect(cardText).toContain(card("page-c"));
|
|
1118
1131
|
// …and the persisted metadata still carries the pruned cards (the filter
|
|
1119
1132
|
// is rehydration-time, never a metadata rewrite).
|
|
1120
1133
|
const metadata = testSqlite
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for the memory-v3 injection layer (`injector.ts`): frozen net-new
|
|
3
|
-
* cards +
|
|
3
|
+
* cards + per-turn spotlight.
|
|
4
4
|
*
|
|
5
5
|
* - net-new dedup: a turn re-selecting already-injected pages renders zero
|
|
6
6
|
* new cards (empty-text block — still produced, so v2 suppression holds);
|
|
@@ -613,14 +613,14 @@ describe("memoryV3Injector — frozen net-new cards", () => {
|
|
|
613
613
|
});
|
|
614
614
|
});
|
|
615
615
|
|
|
616
|
-
// ───
|
|
616
|
+
// ─── per-turn spotlight ─────────────────────────────────────────────────────
|
|
617
617
|
|
|
618
618
|
describe("memoryV3SpotlightInjector — ephemeral section spotlight", () => {
|
|
619
619
|
const sectionA = section("page-a", "Alpha", "alpha section text");
|
|
620
620
|
const sectionB = section("page-b", "Beta", "beta section text");
|
|
621
621
|
const sectionC = section("page-c", "Gamma", "gamma section text");
|
|
622
622
|
|
|
623
|
-
test("renders selected finder hits' matched sections
|
|
623
|
+
test("renders selected finder hits' matched sections as an after-memory-prefix spotlight", async () => {
|
|
624
624
|
liveEnabled = true;
|
|
625
625
|
turnResults.set(0, result(["page-a", "page-b"], [["page-a", sectionA]]));
|
|
626
626
|
|
|
@@ -36,6 +36,28 @@ describe("renderCardsBlockInner", () => {
|
|
|
36
36
|
test("empty card list renders the empty string (no header-only block)", () => {
|
|
37
37
|
expect(renderCardsBlockInner([])).toBe("");
|
|
38
38
|
});
|
|
39
|
+
|
|
40
|
+
test("skill cards get a one-shot catalog hint that is not a concept card", () => {
|
|
41
|
+
const inner = renderCardsBlockInner([
|
|
42
|
+
"# Skill: telegram-setup\nSet up Telegram.",
|
|
43
|
+
"# memory/concepts/page-a.md\nhead a",
|
|
44
|
+
]);
|
|
45
|
+
expect(inner).toContain(V3_CARDS_INJECTION_HEADER);
|
|
46
|
+
expect(inner).toContain("# Skills\n");
|
|
47
|
+
expect(inner).toContain("assistant plugins search <name>");
|
|
48
|
+
expect(inner).toContain("assistant skills search <name>");
|
|
49
|
+
expect(inner).toContain("currently in the workspace");
|
|
50
|
+
expect(inner.indexOf("# Skills")).toBeLessThan(inner.indexOf("# Skill:"));
|
|
51
|
+
expect(inner.startsWith(`${V3_CARDS_INJECTION_HEADER}\n\n# Skills\n`)).toBe(
|
|
52
|
+
true,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("concept-only cards omit the skill catalog hint", () => {
|
|
57
|
+
const inner = renderCardsBlockInner(["# memory/concepts/page-a.md\nhead a"]);
|
|
58
|
+
expect(inner).not.toContain("# Skills\n");
|
|
59
|
+
expect(inner).not.toContain("assistant plugins search");
|
|
60
|
+
});
|
|
39
61
|
});
|
|
40
62
|
|
|
41
63
|
describe("renderSpotlightInner", () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The memory-v3 {@link Injector}s: frozen net-new cards +
|
|
2
|
+
* The memory-v3 {@link Injector}s: frozen net-new cards + per-turn spotlight.
|
|
3
3
|
*
|
|
4
4
|
* Two injectors share one orchestration result per turn (memoized via
|
|
5
5
|
* {@link observeTurnOnce} so re-entry assemblies — overflow convergence,
|
|
@@ -30,15 +30,18 @@
|
|
|
30
30
|
* frozen cards still ride history).
|
|
31
31
|
*
|
|
32
32
|
* - {@link memoryV3SpotlightInjector} (id `memory-v3-spotlight`,
|
|
33
|
-
* `after-memory-prefix`): the
|
|
34
|
-
* selected finder hits' matched sections, plus the previous
|
|
33
|
+
* `after-memory-prefix`): the per-turn spotlight layer. Renders the top
|
|
34
|
+
* `spotlight.n` selected finder hits' matched sections, plus the previous
|
|
35
35
|
* `spotlight.windowTurns` turns' entries from an in-memory per-conversation
|
|
36
36
|
* ring (a daemon restart simply re-warms it), as a `<memory_spotlight>`
|
|
37
|
-
* block
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* block. Runtime assembly splices the block onto the current user message
|
|
38
|
+
* immediately after any frozen `<memory>` cards. The user-prompt-submit
|
|
39
|
+
* hook persists the wrapped text under `metadata.memoryV3SpotlightBlock`;
|
|
40
|
+
* `conversation.ts` rehydrates it on load. Historical user messages keep
|
|
41
|
+
* the spotlight they were sent with, so the provider prefix through those
|
|
42
|
+
* messages stays byte-identical. Mid-turn re-entry and post-compact
|
|
43
|
+
* tail-strip the current tail before splicing a fresh spotlight so the
|
|
44
|
+
* block does not double-stack.
|
|
42
45
|
*
|
|
43
46
|
* Gating: `memory.v3.live` (config) runs orchestration and attaches blocks;
|
|
44
47
|
* with it off, no orchestration runs and nothing is attached.
|
|
@@ -442,13 +445,9 @@ export const memoryV3SpotlightInjector: Injector = {
|
|
|
442
445
|
return {
|
|
443
446
|
id: MEMORY_V3_SPOTLIGHT_BLOCK_ID,
|
|
444
447
|
text: wrapMemorySpotlightBlock(renderSpotlightInner(window)),
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
// spotlight lands immediately after the cards rather than at the user
|
|
449
|
-
// tail). Cache-neutral: the block is strip-and-replaced from prior
|
|
450
|
-
// messages by block id every turn regardless of placement, so the
|
|
451
|
-
// frozen card prefix stays byte-stable and cached.
|
|
448
|
+
// Immediately after frozen `<memory>` cards. `countMemoryPrefixBlocks`
|
|
449
|
+
// counts `<memory>` but not `<memory_spotlight>`, so this lands
|
|
450
|
+
// between the cards and NOW.md / user text.
|
|
452
451
|
placement: "after-memory-prefix",
|
|
453
452
|
};
|
|
454
453
|
} catch (err) {
|
|
@@ -190,6 +190,18 @@ describe("parseCardSections / filterPrunedCardSections", () => {
|
|
|
190
190
|
expect(parsed.sections[1]!.text).toBe(card("page-b"));
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
+
test("skill catalog hint is a non-card piece and leaves the read-affordance preamble intact", () => {
|
|
194
|
+
const mixed = renderCardsBlockInner([
|
|
195
|
+
"# Skill: telegram-setup\nSet up Telegram.",
|
|
196
|
+
card("page-a"),
|
|
197
|
+
]);
|
|
198
|
+
const parsed = parseCardSections(mixed);
|
|
199
|
+
expect(parsed.preamble).toBe(V3_CARDS_INJECTION_HEADER);
|
|
200
|
+
expect(parsed.sections.map((s) => s.slug)).toEqual(["page-a"]);
|
|
201
|
+
expect(parsed.pieces.some((piece) => piece.kind === "other")).toBe(true);
|
|
202
|
+
expect(mixed).toContain("assistant plugins search <name>");
|
|
203
|
+
});
|
|
204
|
+
|
|
193
205
|
test("no pruned slug present → returns the SAME reference (no-op)", () => {
|
|
194
206
|
expect(filterPrunedCardSections(inner, new Set(["page-z"]))).toBe(inner);
|
|
195
207
|
expect(filterPrunedCardSections(inner, new Set())).toBe(inner);
|
|
@@ -228,8 +240,14 @@ describe("parseCardSections / filterPrunedCardSections", () => {
|
|
|
228
240
|
expect(parsed.sections.map((s) => s.slug)).toEqual(["page-a", "page-b"]);
|
|
229
241
|
// page-a's section stops AT the capability header — it must not absorb it.
|
|
230
242
|
expect(parsed.sections[0]!.text).toBe(card("page-a"));
|
|
231
|
-
expect(parsed.pieces.map((p) => p.kind)).toEqual([
|
|
232
|
-
|
|
243
|
+
expect(parsed.pieces.map((p) => p.kind)).toEqual([
|
|
244
|
+
"other",
|
|
245
|
+
"card",
|
|
246
|
+
"other",
|
|
247
|
+
"card",
|
|
248
|
+
]);
|
|
249
|
+
expect(parsed.pieces[0]!.text).toContain("assistant plugins search <name>");
|
|
250
|
+
expect(parsed.pieces[2]!.text).toBe(CAPABILITY_CHUNK);
|
|
233
251
|
});
|
|
234
252
|
|
|
235
253
|
test("pruning a concept card never swallows a trailing capability chunk", () => {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { wrapMemoryBlock } from "../memory-marker.js";
|
|
2
|
+
import { SKILLS_INJECTION_CATALOG_HINT } from "../substrate/skill-content.js";
|
|
2
3
|
import { Section, Slug } from "./types.js";
|
|
3
4
|
|
|
5
|
+
/** Own `# ` chunk so prune treats the catalog hint as a non-card piece. */
|
|
6
|
+
const SKILLS_CATALOG_HINT_CHUNK = `# Skills\n${SKILLS_INJECTION_CATALOG_HINT}`;
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* Leading instruction line of the frozen card block — byte-identical to v2's
|
|
6
10
|
* `INJECTION_HEADER` (`memory/v2/injection.ts`) so the read affordance the
|
|
@@ -23,7 +27,12 @@ export function renderCardsBlockInner(cards: string[]): string {
|
|
|
23
27
|
if (cards.length === 0) {
|
|
24
28
|
return "";
|
|
25
29
|
}
|
|
26
|
-
|
|
30
|
+
const parts = [V3_CARDS_INJECTION_HEADER];
|
|
31
|
+
if (cards.some((card) => card.startsWith("# Skill:"))) {
|
|
32
|
+
parts.push(SKILLS_CATALOG_HINT_CHUNK);
|
|
33
|
+
}
|
|
34
|
+
parts.push(...cards);
|
|
35
|
+
return parts.join("\n\n");
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
/**
|
|
@@ -23,13 +23,24 @@ export const MEMORY_V3_BLOCK_ID = "memory-v3" as const;
|
|
|
23
23
|
export const MEMORY_V3_COMMIT_META_KEY = "memoryV3Commit" as const;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Injection-block id for the v3
|
|
27
|
-
* current window's matched sections
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* Injection-block id for the v3 per-turn `<memory_spotlight>` block (the
|
|
27
|
+
* current window's matched sections). Distinct from {@link MEMORY_V3_BLOCK_ID}:
|
|
28
|
+
* the spotlight never participates in v2 suppression. Each turn's block stays
|
|
29
|
+
* on the user message that was sent with it.
|
|
30
30
|
*/
|
|
31
31
|
export const MEMORY_V3_SPOTLIGHT_BLOCK_ID = "memory-v3-spotlight" as const;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Message-metadata key for the wrapped `<memory_spotlight>` block persisted
|
|
35
|
+
* on the user row that received it. `loadFromDb` rehydrates from this key so
|
|
36
|
+
* historical turns keep the spotlight they were sent with. Kept as a
|
|
37
|
+
* literal in `messageMetadataSchema` (same pattern as
|
|
38
|
+
* `memoryV3InjectedBlock`) so the storage schema does not import the
|
|
39
|
+
* memory feature.
|
|
40
|
+
*/
|
|
41
|
+
export const MEMORY_V3_SPOTLIGHT_BLOCK_METADATA_KEY =
|
|
42
|
+
"memoryV3SpotlightBlock" as const;
|
|
43
|
+
|
|
33
44
|
/**
|
|
34
45
|
* A single section of a page: the lead (text before the first `## heading`,
|
|
35
46
|
* ordinal 0) or a heading-delimited block. Over-long sections are split into
|
|
@@ -37,6 +37,9 @@ const unifiedTurnContextInjector: Injector = {
|
|
|
37
37
|
clientOs: ctx.clientOs,
|
|
38
38
|
visibleApp: ctx.visibleApp,
|
|
39
39
|
channelName: ctx.channelName,
|
|
40
|
+
// The chat and thread this turn arrived through, from its trust snapshot.
|
|
41
|
+
chatId: ctx.trust.requesterChatId,
|
|
42
|
+
threadId: ctx.trust.sourceThreadId,
|
|
40
43
|
actorContext: ctx.actorContext,
|
|
41
44
|
configuredUserTimezone: ctx.configuredUserTimezone,
|
|
42
45
|
clientTimezone: ctx.clientTimezone,
|
|
@@ -33,6 +33,19 @@ export interface UnifiedTurnContextOptions {
|
|
|
33
33
|
pluginName?: string;
|
|
34
34
|
} | null;
|
|
35
35
|
channelName?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Channel-native id of the chat this turn arrived through (a Slack `D…` or
|
|
38
|
+
* `C…` id, a Telegram chat id). Rendered as the `chat_id:` line for channel
|
|
39
|
+
* turns only, so a request that needs the chat's own history starts from
|
|
40
|
+
* the id the turn already carries instead of discovering it.
|
|
41
|
+
*/
|
|
42
|
+
chatId?: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Channel-native thread id of the message that started this turn, when it
|
|
45
|
+
* arrived in a thread (a Slack `thread_ts`). Rendered as the `thread_id:`
|
|
46
|
+
* line beside `chat_id:`; absent for an un-threaded turn.
|
|
47
|
+
*/
|
|
48
|
+
threadId?: string | null;
|
|
36
49
|
actorContext?: InboundActorContext | null;
|
|
37
50
|
configuredUserTimezone?: string | null;
|
|
38
51
|
clientTimezone?: string | null;
|
|
@@ -130,6 +143,17 @@ export function buildUnifiedTurnContextBlock(
|
|
|
130
143
|
);
|
|
131
144
|
}
|
|
132
145
|
|
|
146
|
+
// Where the turn is: the chat, and the thread within it when there is one.
|
|
147
|
+
// Channel turns only; an app turn has no external chat.
|
|
148
|
+
if (options.channelName && options.channelName !== "vellum") {
|
|
149
|
+
if (options.chatId) {
|
|
150
|
+
lines.push(`chat_id: ${sanitizeInlineContextValue(options.chatId)}`);
|
|
151
|
+
}
|
|
152
|
+
if (options.threadId) {
|
|
153
|
+
lines.push(`thread_id: ${sanitizeInlineContextValue(options.threadId)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
// Actor identity and trust fields — only for non-guardian turns.
|
|
134
158
|
if (options.actorContext) {
|
|
135
159
|
const ctx = options.actorContext;
|
|
@@ -93,13 +93,25 @@ const PluginStdioServerSchema = z.object({
|
|
|
93
93
|
cwd: z.string().optional(),
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Remote MCP entry. Agent Plugins 1.0.0 requires `type` and names no
|
|
98
|
+
* default. MCP's current remote transport is Streamable HTTP, so this
|
|
99
|
+
* host fills omitted `type` and the Claude-style `http` alias as
|
|
100
|
+
* `streamable-http`. `sse` stays explicit.
|
|
101
|
+
*/
|
|
96
102
|
const PluginHttpServerSchema = z.object({
|
|
97
|
-
type: z
|
|
103
|
+
type: z
|
|
104
|
+
.union([
|
|
105
|
+
z.literal("streamable-http"),
|
|
106
|
+
z.literal("sse"),
|
|
107
|
+
z.literal("http").transform(() => "streamable-http" as const),
|
|
108
|
+
])
|
|
109
|
+
.default("streamable-http"),
|
|
98
110
|
url: z.string().min(1),
|
|
99
111
|
headers: z.record(z.string(), z.string()).optional(),
|
|
100
112
|
});
|
|
101
113
|
|
|
102
|
-
const PluginMcpServerSchema = z.
|
|
114
|
+
const PluginMcpServerSchema = z.union([
|
|
103
115
|
PluginStdioServerSchema,
|
|
104
116
|
PluginHttpServerSchema,
|
|
105
117
|
]);
|