@vellumai/assistant 0.12.1 → 0.12.2-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +327 -52
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +4 -2
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/heartbeat-service.test.ts +56 -40
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-cu-proxy.test.ts +539 -0
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +113 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/service-account-token.test.ts +247 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/unicode.test.ts +13 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/agent/loop.ts +1 -0
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/avatar/identity-avatar.ts +7 -2
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +24 -12
- package/src/config/loader.ts +1 -0
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/credential-health/credential-health-service.ts +11 -17
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +2 -1
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +15 -9
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +186 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +73 -0
- package/src/oauth/connection-resolver.ts +95 -2
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +204 -25
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +4 -2
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/host-utils.ts +10 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/host-observe.test.ts +40 -1
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +36 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/security/untrusted-content.ts +3 -1
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/util/unicode.ts +15 -1
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -115,6 +115,11 @@ For everything else in your review window, use the \`remember\` tool on facts, p
|
|
|
115
115
|
expect(out).toContain(
|
|
116
116
|
"\n---\n\nIf your review window contains a PROCEDURE you actually carried out",
|
|
117
117
|
);
|
|
118
|
+
// An UPDATE is announced by a notice whose only account of the change is
|
|
119
|
+
// what the pass passes here, so the instruction has to ask for it.
|
|
120
|
+
expect(out).toContain(
|
|
121
|
+
"and pass `change_summary` (the update is rejected without it): one or two short sentences (under 200 characters)",
|
|
122
|
+
);
|
|
118
123
|
expect(
|
|
119
124
|
out.endsWith(
|
|
120
125
|
"skills are for executed, reusable procedures, not for facts.\n",
|
|
@@ -6,16 +6,22 @@ import { setConfig } from "../../../../__tests__/helpers/set-config.js";
|
|
|
6
6
|
// pipeline (both flags default true under the real loader).
|
|
7
7
|
setConfig("memory", { enabled: false, v2: { enabled: false } });
|
|
8
8
|
|
|
9
|
+
import { createConversation } from "../../../../persistence/conversation-crud.js";
|
|
9
10
|
import {
|
|
10
11
|
getDb,
|
|
11
12
|
getMemorySqlite,
|
|
13
|
+
resetDb,
|
|
12
14
|
} from "../../../../persistence/db-connection.js";
|
|
13
15
|
import { initializeDb } from "../../../../persistence/db-init.js";
|
|
16
|
+
import { messages } from "../../../../persistence/schema/index.js";
|
|
14
17
|
import {
|
|
18
|
+
_resetRetrospectiveCursorColumnForTests,
|
|
15
19
|
appendToRememberedLog,
|
|
16
20
|
bumpRetrospectiveLastRunAt,
|
|
21
|
+
ensureRetrospectiveCursorColumn,
|
|
17
22
|
forkRetrospectiveState,
|
|
18
23
|
getRetrospectiveState,
|
|
24
|
+
listRetrospectiveStates,
|
|
19
25
|
REMEMBERED_LOG_MAX_BYTES,
|
|
20
26
|
REMEMBERED_LOG_MAX_ENTRIES,
|
|
21
27
|
upsertRetrospectiveState,
|
|
@@ -176,6 +182,219 @@ describe("memory-retrospective-state remembered log persistence", () => {
|
|
|
176
182
|
});
|
|
177
183
|
});
|
|
178
184
|
|
|
185
|
+
describe("memory-retrospective-state cursor timestamp", () => {
|
|
186
|
+
beforeEach(() => {
|
|
187
|
+
resetTables();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("upsert persists lastProcessedCreatedAt and both reads return it", async () => {
|
|
191
|
+
await upsertRetrospectiveState({
|
|
192
|
+
conversationId: "conv-ts",
|
|
193
|
+
lastProcessedMessageId: "m1",
|
|
194
|
+
lastProcessedCreatedAt: 1_234,
|
|
195
|
+
lastRunAt: 1000,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(getRetrospectiveState("conv-ts")?.lastProcessedCreatedAt).toBe(
|
|
199
|
+
1_234,
|
|
200
|
+
);
|
|
201
|
+
expect(
|
|
202
|
+
listRetrospectiveStates(10).find((s) => s.conversationId === "conv-ts")
|
|
203
|
+
?.lastProcessedCreatedAt,
|
|
204
|
+
).toBe(1_234);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("an upsert that omits the timestamp records unknown for the new pointer", async () => {
|
|
208
|
+
await upsertRetrospectiveState({
|
|
209
|
+
conversationId: "conv-ts",
|
|
210
|
+
lastProcessedMessageId: "m1",
|
|
211
|
+
lastProcessedCreatedAt: 1_234,
|
|
212
|
+
lastRunAt: 1000,
|
|
213
|
+
});
|
|
214
|
+
await upsertRetrospectiveState({
|
|
215
|
+
conversationId: "conv-ts",
|
|
216
|
+
lastProcessedMessageId: "m2",
|
|
217
|
+
lastRunAt: 2000,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const state = getRetrospectiveState("conv-ts");
|
|
221
|
+
expect(state?.lastProcessedMessageId).toBe("m2");
|
|
222
|
+
expect(state?.lastProcessedCreatedAt).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("bumpRetrospectiveLastRunAt leaves the timestamp untouched", async () => {
|
|
226
|
+
await upsertRetrospectiveState({
|
|
227
|
+
conversationId: "conv-ts",
|
|
228
|
+
lastProcessedMessageId: "m1",
|
|
229
|
+
lastProcessedCreatedAt: 1_234,
|
|
230
|
+
lastRunAt: 1000,
|
|
231
|
+
});
|
|
232
|
+
await bumpRetrospectiveLastRunAt("conv-ts", 5000);
|
|
233
|
+
|
|
234
|
+
const state = getRetrospectiveState("conv-ts");
|
|
235
|
+
expect(state?.lastRunAt).toBe(5000);
|
|
236
|
+
expect(state?.lastProcessedCreatedAt).toBe(1_234);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("forking carries the forked pointer's createdAt, read through the main handle", async () => {
|
|
240
|
+
const db = getDb();
|
|
241
|
+
createConversation({ id: "conv-fork-ts-src" });
|
|
242
|
+
createConversation({ id: "conv-fork-ts-dst" });
|
|
243
|
+
for (const [id, conversationId] of [
|
|
244
|
+
["src-m1", "conv-fork-ts-src"],
|
|
245
|
+
["dst-m1", "conv-fork-ts-dst"],
|
|
246
|
+
] as const) {
|
|
247
|
+
db.insert(messages)
|
|
248
|
+
.values({
|
|
249
|
+
id,
|
|
250
|
+
conversationId,
|
|
251
|
+
role: "user",
|
|
252
|
+
content: "[]",
|
|
253
|
+
createdAt: 4_000,
|
|
254
|
+
metadata: null,
|
|
255
|
+
})
|
|
256
|
+
.run();
|
|
257
|
+
}
|
|
258
|
+
await upsertRetrospectiveState({
|
|
259
|
+
conversationId: "conv-fork-ts-src",
|
|
260
|
+
lastProcessedMessageId: "src-m1",
|
|
261
|
+
lastProcessedCreatedAt: 4_000,
|
|
262
|
+
lastRunAt: 1,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
forkRetrospectiveState({
|
|
266
|
+
database: db,
|
|
267
|
+
sourceConversationId: "conv-fork-ts-src",
|
|
268
|
+
forkedConversationId: "conv-fork-ts-dst",
|
|
269
|
+
forkedMessageIds: new Map([["src-m1", "dst-m1"]]),
|
|
270
|
+
lastCopiedSourceMessageId: "src-m1",
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const child = getRetrospectiveState("conv-fork-ts-dst");
|
|
274
|
+
expect(child?.lastProcessedMessageId).toBe("dst-m1");
|
|
275
|
+
expect(child?.lastProcessedCreatedAt).toBe(4_000);
|
|
276
|
+
|
|
277
|
+
db.run(`DELETE FROM messages`);
|
|
278
|
+
db.run(`DELETE FROM conversations`);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("a source cursor whose row was regenerated away is placed by its stored bound, not clamped past the copies", async () => {
|
|
282
|
+
const db = getDb();
|
|
283
|
+
createConversation({ id: "conv-fork-bound-src" });
|
|
284
|
+
createConversation({ id: "conv-fork-bound-dst" });
|
|
285
|
+
// src-m2 (createdAt 2_000) was the cursor; a regenerate deleted it and
|
|
286
|
+
// src-m3 replaced it. The fork copies src-m1 and src-m3.
|
|
287
|
+
for (const [id, conversationId, createdAt] of [
|
|
288
|
+
["src-m1", "conv-fork-bound-src", 1_000],
|
|
289
|
+
["src-m3", "conv-fork-bound-src", 3_000],
|
|
290
|
+
["dst-m1", "conv-fork-bound-dst", 1_000],
|
|
291
|
+
["dst-m3", "conv-fork-bound-dst", 3_000],
|
|
292
|
+
] as const) {
|
|
293
|
+
db.insert(messages)
|
|
294
|
+
.values({
|
|
295
|
+
id,
|
|
296
|
+
conversationId,
|
|
297
|
+
role: "user",
|
|
298
|
+
content: "[]",
|
|
299
|
+
createdAt,
|
|
300
|
+
metadata: null,
|
|
301
|
+
})
|
|
302
|
+
.run();
|
|
303
|
+
}
|
|
304
|
+
await upsertRetrospectiveState({
|
|
305
|
+
conversationId: "conv-fork-bound-src",
|
|
306
|
+
lastProcessedMessageId: "src-m2",
|
|
307
|
+
lastProcessedCreatedAt: 2_000,
|
|
308
|
+
lastRunAt: 1,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
forkRetrospectiveState({
|
|
312
|
+
database: db,
|
|
313
|
+
sourceConversationId: "conv-fork-bound-src",
|
|
314
|
+
forkedConversationId: "conv-fork-bound-dst",
|
|
315
|
+
forkedMessageIds: new Map([
|
|
316
|
+
["src-m1", "dst-m1"],
|
|
317
|
+
["src-m3", "dst-m3"],
|
|
318
|
+
]),
|
|
319
|
+
lastCopiedSourceMessageId: "src-m3",
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const child = getRetrospectiveState("conv-fork-bound-dst");
|
|
323
|
+
expect(child?.lastProcessedMessageId).toBe("dst-m1");
|
|
324
|
+
expect(child?.lastProcessedCreatedAt).toBe(1_000);
|
|
325
|
+
|
|
326
|
+
// A bound before every copied row leaves the child with nothing processed.
|
|
327
|
+
await upsertRetrospectiveState({
|
|
328
|
+
conversationId: "conv-fork-bound-src",
|
|
329
|
+
lastProcessedMessageId: "src-m0",
|
|
330
|
+
lastProcessedCreatedAt: 500,
|
|
331
|
+
lastRunAt: 1,
|
|
332
|
+
});
|
|
333
|
+
forkRetrospectiveState({
|
|
334
|
+
database: db,
|
|
335
|
+
sourceConversationId: "conv-fork-bound-src",
|
|
336
|
+
forkedConversationId: "conv-fork-bound-dst",
|
|
337
|
+
forkedMessageIds: new Map([
|
|
338
|
+
["src-m1", "dst-m1"],
|
|
339
|
+
["src-m3", "dst-m3"],
|
|
340
|
+
]),
|
|
341
|
+
lastCopiedSourceMessageId: "src-m3",
|
|
342
|
+
});
|
|
343
|
+
expect(
|
|
344
|
+
getRetrospectiveState("conv-fork-bound-dst")?.lastProcessedMessageId,
|
|
345
|
+
).toBe("");
|
|
346
|
+
|
|
347
|
+
db.run(`DELETE FROM messages`);
|
|
348
|
+
db.run(`DELETE FROM conversations`);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("ensureRetrospectiveCursorColumn adds the column to a legacy table once and is a no-op after", () => {
|
|
352
|
+
const raw = getMemorySqlite()!;
|
|
353
|
+
raw.exec("DROP TABLE memory_retrospective_state");
|
|
354
|
+
raw.exec(`
|
|
355
|
+
CREATE TABLE memory_retrospective_state (
|
|
356
|
+
conversation_id TEXT PRIMARY KEY,
|
|
357
|
+
last_processed_message_id TEXT NOT NULL,
|
|
358
|
+
last_run_at INTEGER NOT NULL,
|
|
359
|
+
remembered_log TEXT
|
|
360
|
+
)
|
|
361
|
+
`);
|
|
362
|
+
_resetRetrospectiveCursorColumnForTests();
|
|
363
|
+
|
|
364
|
+
expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
|
|
365
|
+
expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
|
|
366
|
+
|
|
367
|
+
const columns = raw
|
|
368
|
+
.query("PRAGMA table_info(memory_retrospective_state)")
|
|
369
|
+
.all() as Array<{ name: string }>;
|
|
370
|
+
expect(
|
|
371
|
+
columns.filter((c) => c.name === "last_processed_created_at"),
|
|
372
|
+
).toHaveLength(1);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("a replaced memory connection is probed again, so a restored database without the column gets it", () => {
|
|
376
|
+
expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
|
|
377
|
+
|
|
378
|
+
// What a restore or import does: the stored connections are dropped and
|
|
379
|
+
// reopened. Strip the column on the reopened file to stand in for an
|
|
380
|
+
// older database.
|
|
381
|
+
resetDb();
|
|
382
|
+
const reopened = getMemorySqlite()!;
|
|
383
|
+
reopened.exec(
|
|
384
|
+
"ALTER TABLE memory_retrospective_state DROP COLUMN last_processed_created_at",
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
expect(getRetrospectiveState("conv-none")).toBeNull();
|
|
388
|
+
expect(ensureRetrospectiveCursorColumn("test")).toBe(true);
|
|
389
|
+
const columns = reopened
|
|
390
|
+
.query("PRAGMA table_info(memory_retrospective_state)")
|
|
391
|
+
.all() as Array<{ name: string }>;
|
|
392
|
+
expect(columns.some((c) => c.name === "last_processed_created_at")).toBe(
|
|
393
|
+
true,
|
|
394
|
+
);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
179
398
|
describe("fail-soft when the underlying statement fails", () => {
|
|
180
399
|
// The memory connection is present, but the relocated table is gone (a
|
|
181
400
|
// corrupt/dropped table, SQLITE_FULL, I/O error, or SQLITE_BUSY after
|
|
@@ -412,3 +412,34 @@ describe("listSweepCandidateConversationIds", () => {
|
|
|
412
412
|
]);
|
|
413
413
|
});
|
|
414
414
|
});
|
|
415
|
+
|
|
416
|
+
describe("runRetrospectiveSweep: cursor survives a regenerated reply", () => {
|
|
417
|
+
beforeEach(() => {
|
|
418
|
+
resetTables();
|
|
419
|
+
enqueueCalls = [];
|
|
420
|
+
enqueueDeclines = new Set();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("a stale conversation whose cursor row was regenerated away is still swept", async () => {
|
|
424
|
+
const conv = createConversation({ id: "conv-a" });
|
|
425
|
+
insertMessage(conv.id, { createdAt: 1_000 });
|
|
426
|
+
const reply = insertMessage(conv.id, {
|
|
427
|
+
role: "assistant",
|
|
428
|
+
createdAt: 2_000,
|
|
429
|
+
});
|
|
430
|
+
await upsertRetrospectiveState({
|
|
431
|
+
conversationId: conv.id,
|
|
432
|
+
lastProcessedMessageId: reply,
|
|
433
|
+
lastProcessedCreatedAt: 2_000,
|
|
434
|
+
lastRunAt: Date.now() - SWEEP_INTERVAL_MS - 60_000,
|
|
435
|
+
});
|
|
436
|
+
getDb().delete(messages).where(eq(messages.id, reply)).run();
|
|
437
|
+
insertMessage(conv.id, { role: "assistant", createdAt: 3_000 });
|
|
438
|
+
|
|
439
|
+
await runRetrospectiveSweep(makeConfig());
|
|
440
|
+
|
|
441
|
+
expect(enqueueCalls).toEqual([
|
|
442
|
+
{ conversationId: conv.id, trigger: "sweep" },
|
|
443
|
+
]);
|
|
444
|
+
});
|
|
445
|
+
});
|
|
@@ -12,6 +12,8 @@ mock.module("../memory-retrospective-enqueue.js", () => ({
|
|
|
12
12
|
},
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
+
import { eq } from "drizzle-orm";
|
|
16
|
+
|
|
15
17
|
import type { AssistantConfig } from "../../../../config/types.js";
|
|
16
18
|
import { createConversation } from "../../../../persistence/conversation-crud.js";
|
|
17
19
|
import {
|
|
@@ -307,3 +309,64 @@ describe("maybeEnqueueRetrospective — kind-aware accounting", () => {
|
|
|
307
309
|
]);
|
|
308
310
|
});
|
|
309
311
|
});
|
|
312
|
+
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// maybeEnqueueRetrospective: the cursor survives a regenerated reply.
|
|
315
|
+
//
|
|
316
|
+
// The cursor normally lands on the latest assistant reply, and a regenerate
|
|
317
|
+
// deletes exactly that row. With its `createdAt` persisted the cursor still
|
|
318
|
+
// bounds the count, so the conversation keeps getting retrospectives.
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
describe("maybeEnqueueRetrospective: cursor survives a regenerated reply", () => {
|
|
322
|
+
beforeEach(() => {
|
|
323
|
+
resetTables();
|
|
324
|
+
enqueueCalls = [];
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("a regenerate that deletes the cursor's row does not stall the conversation", async () => {
|
|
328
|
+
const conv = createConversation("conv");
|
|
329
|
+
insertMessage(conv.id, { createdAt: 1_000 });
|
|
330
|
+
const reply = insertMessage(conv.id, {
|
|
331
|
+
role: "assistant",
|
|
332
|
+
createdAt: 2_000,
|
|
333
|
+
});
|
|
334
|
+
await upsertRetrospectiveState({
|
|
335
|
+
conversationId: conv.id,
|
|
336
|
+
lastProcessedMessageId: reply,
|
|
337
|
+
lastProcessedCreatedAt: 2_000,
|
|
338
|
+
lastRunAt: Date.now() - 24 * 60 * 60_000,
|
|
339
|
+
});
|
|
340
|
+
// Regenerate: the retry route discards the latest assistant turn, then
|
|
341
|
+
// a fresh reply and the next user message land.
|
|
342
|
+
getDb().delete(messages).where(eq(messages.id, reply)).run();
|
|
343
|
+
insertMessage(conv.id, { role: "assistant", createdAt: 3_000 });
|
|
344
|
+
insertMessage(conv.id, { createdAt: 4_000 });
|
|
345
|
+
|
|
346
|
+
maybeEnqueueRetrospective(conv.id, makeConfig());
|
|
347
|
+
|
|
348
|
+
expect(enqueueCalls).toEqual([
|
|
349
|
+
{ conversationId: conv.id, trigger: "interval" },
|
|
350
|
+
]);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("a cursor written without a timestamp still goes quiet once its row is gone", async () => {
|
|
354
|
+
const conv = createConversation("conv");
|
|
355
|
+
insertMessage(conv.id, { createdAt: 1_000 });
|
|
356
|
+
const reply = insertMessage(conv.id, {
|
|
357
|
+
role: "assistant",
|
|
358
|
+
createdAt: 2_000,
|
|
359
|
+
});
|
|
360
|
+
await upsertRetrospectiveState({
|
|
361
|
+
conversationId: conv.id,
|
|
362
|
+
lastProcessedMessageId: reply,
|
|
363
|
+
lastRunAt: Date.now() - 24 * 60 * 60_000,
|
|
364
|
+
});
|
|
365
|
+
getDb().delete(messages).where(eq(messages.id, reply)).run();
|
|
366
|
+
insertMessage(conv.id, { createdAt: 4_000 });
|
|
367
|
+
|
|
368
|
+
maybeEnqueueRetrospective(conv.id, makeConfig());
|
|
369
|
+
|
|
370
|
+
expect(enqueueCalls).toEqual([]);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -14,6 +14,10 @@ mock.module("../worker-control.js", () => ({
|
|
|
14
14
|
},
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
+
mock.module("../skill-md-mtime-poll.js", () => ({
|
|
18
|
+
stopWorkspaceSkillMdMtimePoll: () => {},
|
|
19
|
+
}));
|
|
20
|
+
|
|
17
21
|
const memoryShutdown = (await import("../hooks/shutdown.js")).default;
|
|
18
22
|
|
|
19
23
|
describe("memory shutdown hook", () => {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import { getConfiguredProvider } from "@vellumai/plugin-api";
|
|
8
8
|
|
|
9
9
|
import { redactSecrets } from "../../../../security/secret-scanner.js";
|
|
10
|
+
import { safeStringSlice } from "../host-utils.js";
|
|
10
11
|
import {
|
|
11
12
|
buildRecallAgentPromptBundle,
|
|
12
13
|
FINISH_RECALL_TOOL_DEFINITION,
|
|
@@ -862,9 +863,9 @@ function compactText(text: string, maxChars: number): string {
|
|
|
862
863
|
return compacted;
|
|
863
864
|
}
|
|
864
865
|
if (maxChars <= 3) {
|
|
865
|
-
return compacted
|
|
866
|
+
return safeStringSlice(compacted, 0, maxChars);
|
|
866
867
|
}
|
|
867
|
-
return `${compacted
|
|
868
|
+
return `${safeStringSlice(compacted, 0, maxChars - 3).trimEnd()}...`;
|
|
868
869
|
}
|
|
869
870
|
|
|
870
871
|
function dedupeEvidenceById(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { safeStringSlice } from "../host-utils.js";
|
|
1
2
|
import type {
|
|
2
3
|
DeterministicRecallSearchResult,
|
|
3
4
|
DeterministicRecallSourceNote,
|
|
@@ -139,9 +140,9 @@ function compactText(text: string, maxChars: number): string {
|
|
|
139
140
|
return compacted;
|
|
140
141
|
}
|
|
141
142
|
if (maxChars <= 3) {
|
|
142
|
-
return compacted
|
|
143
|
+
return safeStringSlice(compacted, 0, maxChars);
|
|
143
144
|
}
|
|
144
|
-
return `${compacted
|
|
145
|
+
return `${safeStringSlice(compacted, 0, maxChars - 3).trimEnd()}...`;
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
function dedupeStrings(values: readonly string[]): string[] {
|
|
@@ -27,6 +27,7 @@ import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
|
27
27
|
import { extname, join } from "node:path";
|
|
28
28
|
|
|
29
29
|
import { embedWithRetry } from "../../../../../persistence/embeddings/embed.js";
|
|
30
|
+
import { safeStringSlice } from "../../host-utils.js";
|
|
30
31
|
import { getLogger } from "../../logging.js";
|
|
31
32
|
import { isPathInsideRoot } from "../../path-containment.js";
|
|
32
33
|
import { getEdgeIndex } from "../../substrate/edge-index.js";
|
|
@@ -518,9 +519,11 @@ function buildLexicalExcerpt(
|
|
|
518
519
|
if (focusedLine.length <= MEMORY_V2_LEXICAL_EXCERPT_MAX_CHARS) {
|
|
519
520
|
return focusedLine;
|
|
520
521
|
}
|
|
521
|
-
return `${
|
|
522
|
-
|
|
523
|
-
|
|
522
|
+
return `${safeStringSlice(
|
|
523
|
+
focusedLine,
|
|
524
|
+
0,
|
|
525
|
+
MEMORY_V2_LEXICAL_EXCERPT_MAX_CHARS - 3,
|
|
526
|
+
).trimEnd()}...`;
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
function toLexicalEvidence(match: MemoryV2LexicalMatch): RecallEvidence {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { extname, isAbsolute, join, relative, sep } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { safeStringSlice } from "../../host-utils.js";
|
|
4
5
|
import { isPathInsideRoot } from "../../path-containment.js";
|
|
5
6
|
import type {
|
|
6
7
|
RecallEvidence,
|
|
@@ -924,7 +925,7 @@ function buildExcerpt(lines: readonly string[], lineIndex: number): string {
|
|
|
924
925
|
return focusedLine;
|
|
925
926
|
}
|
|
926
927
|
|
|
927
|
-
return `${focusedLine
|
|
928
|
+
return `${safeStringSlice(focusedLine, 0, EXCERPT_MAX_CHARS - 3).trimEnd()}...`;
|
|
928
929
|
}
|
|
929
930
|
|
|
930
931
|
function buildSectionExcerpt(
|
|
@@ -942,7 +943,7 @@ function buildSectionExcerpt(
|
|
|
942
943
|
return excerpt;
|
|
943
944
|
}
|
|
944
945
|
|
|
945
|
-
return `${excerpt
|
|
946
|
+
return `${safeStringSlice(excerpt, 0, SECTION_EXCERPT_MAX_CHARS - 3).trimEnd()}...`;
|
|
946
947
|
}
|
|
947
948
|
|
|
948
949
|
function buildStructuredJsonExcerpt(contents: string): string {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
loadSkillCatalog,
|
|
17
17
|
type SkillSummary,
|
|
18
18
|
} from "../../../../config/skills.js";
|
|
19
|
+
import { loadWorkspaceMcpConfig } from "../../../../mcp/workspace-mcp-config.js";
|
|
19
20
|
import {
|
|
20
21
|
enqueueMemoryJob,
|
|
21
22
|
upsertEmbedGraphNodeJob,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
getCachedCatalogSync,
|
|
26
27
|
getCatalog,
|
|
27
28
|
} from "../../../../skills/catalog-cache.js";
|
|
29
|
+
import { safeStringSlice } from "../host-utils.js";
|
|
28
30
|
import { getLogger } from "../logging.js";
|
|
29
31
|
import { memoryDbOrNull } from "../memory-db.js";
|
|
30
32
|
import type { SkillCapabilityInput } from "../substrate/skill-content.js";
|
|
@@ -131,12 +133,9 @@ export function seedSkillGraphNodes(): void {
|
|
|
131
133
|
const input = fromSkillSummary(summary);
|
|
132
134
|
|
|
133
135
|
if (summary.id === "mcp-setup") {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
if (names.length > 0) {
|
|
138
|
-
input.description += ` Configured: ${names.join(", ")}`;
|
|
139
|
-
}
|
|
136
|
+
const names = Object.keys(loadWorkspaceMcpConfig().servers);
|
|
137
|
+
if (names.length > 0) {
|
|
138
|
+
input.description += ` Configured: ${names.join(", ")}`;
|
|
140
139
|
}
|
|
141
140
|
}
|
|
142
141
|
|
|
@@ -247,7 +246,7 @@ function buildSkillContent(input: SkillCapabilityInput): string {
|
|
|
247
246
|
content += ` Avoid when: ${input.avoidWhen.join("; ")}.`;
|
|
248
247
|
}
|
|
249
248
|
if (content.length > 500) {
|
|
250
|
-
content = content
|
|
249
|
+
content = safeStringSlice(content, 0, 500);
|
|
251
250
|
}
|
|
252
251
|
return content;
|
|
253
252
|
}
|
|
@@ -17,6 +17,7 @@ import type { HookFunction, InitContext } from "@vellumai/plugin-api";
|
|
|
17
17
|
import { getConfig } from "../../../../config/loader.js";
|
|
18
18
|
import { registerMemoryPluginJobHandlers } from "../job-handler-registration.js";
|
|
19
19
|
import { getLogger } from "../logging.js";
|
|
20
|
+
import { backfillRetrospectiveCursorTimestamps } from "../memory-retrospective-cursor-backfill.js";
|
|
20
21
|
import { runMemoryStartup } from "../startup.js";
|
|
21
22
|
import { ensureMemoryV3PluginSchema } from "../v3/plugin-schema.js";
|
|
22
23
|
|
|
@@ -30,6 +31,15 @@ const init: HookFunction<InitContext> = async () => {
|
|
|
30
31
|
// against an empty table and failed as an unknown type.
|
|
31
32
|
registerMemoryPluginJobHandlers();
|
|
32
33
|
|
|
34
|
+
// Retrospective cursors written before the timestamp column existed can
|
|
35
|
+
// only be completed while their message rows still exist, so start the
|
|
36
|
+
// fill at boot, ahead of any client regenerating a reply. Detached and
|
|
37
|
+
// fail-open like the rest of memory startup; the memory worker runs the
|
|
38
|
+
// same idempotent pass as a second chance.
|
|
39
|
+
void backfillRetrospectiveCursorTimestamps().catch((err) =>
|
|
40
|
+
log.warn({ err }, "Retrospective cursor timestamp backfill failed"),
|
|
41
|
+
);
|
|
42
|
+
|
|
33
43
|
// The memory-v3 plugin-owned schema on the memory connection
|
|
34
44
|
// (`v3/plugin-schema.ts`): the pools and injected-sections tables and the
|
|
35
45
|
// selection log's `section_key` column, ensured idempotently on every
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `memory` message-deleted hook.
|
|
3
|
+
*
|
|
4
|
+
* Stamps the conversation's retrospective cursor with the deleted row's
|
|
5
|
+
* `createdAt` when the cursor points at that row and was written without a
|
|
6
|
+
* timestamp (by a build before the column existed). The cursor usually sits
|
|
7
|
+
* on the latest assistant reply, which is exactly the row a regenerate
|
|
8
|
+
* deletes, and once the row is gone only the timestamp can place it.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { HookFunction, MessageDeletedContext } from "@vellumai/plugin-api";
|
|
12
|
+
|
|
13
|
+
import { preserveRetrospectiveCursorTimestamps } from "../memory-retrospective-cursor-preserve.js";
|
|
14
|
+
|
|
15
|
+
const messageDeleted: HookFunction<MessageDeletedContext> = async (ctx) => {
|
|
16
|
+
await preserveRetrospectiveCursorTimestamps(ctx.conversationId, [
|
|
17
|
+
{ id: ctx.messageId, createdAt: ctx.createdAt },
|
|
18
|
+
]);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default messageDeleted;
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import type { HookFunction, ShutdownContext } from "@vellumai/plugin-api";
|
|
11
11
|
|
|
12
|
+
import { stopWorkspaceSkillMdMtimePoll } from "../skill-md-mtime-poll.js";
|
|
12
13
|
import { stopMemoryWorkerProcess } from "../worker-control.js";
|
|
13
14
|
|
|
14
15
|
const shutdown: HookFunction<ShutdownContext> = async () => {
|
|
16
|
+
stopWorkspaceSkillMdMtimePoll();
|
|
15
17
|
stopMemoryWorkerProcess();
|
|
16
18
|
};
|
|
17
19
|
|
|
@@ -25,6 +25,7 @@ import type { SqliteRetryOptions } from "../../../util/sqlite-retry.js";
|
|
|
25
25
|
import * as hostSqliteRetry from "../../../util/sqlite-retry.js";
|
|
26
26
|
import * as hostStripCommentLines from "../../../util/strip-comment-lines.js";
|
|
27
27
|
import * as hostTruncate from "../../../util/truncate.js";
|
|
28
|
+
import * as hostUnicode from "../../../util/unicode.js";
|
|
28
29
|
import * as hostWorkerCompute from "../../../util/worker-compute.js";
|
|
29
30
|
import * as hostWorkerMemory from "../../../util/worker-memory.js";
|
|
30
31
|
|
|
@@ -76,6 +77,15 @@ export function truncate(str: string, maxLen: number, suffix?: string): string {
|
|
|
76
77
|
: hostTruncate.truncate(str, maxLen, suffix);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
/** `String.prototype.slice` that never cuts a UTF-16 surrogate pair in half. */
|
|
81
|
+
export function safeStringSlice(
|
|
82
|
+
str: string,
|
|
83
|
+
start?: number,
|
|
84
|
+
end?: number,
|
|
85
|
+
): string {
|
|
86
|
+
return hostUnicode.safeStringSlice(str, start, end);
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
export function workerMemoryEnv(): Record<string, string | undefined> {
|
|
80
90
|
return hostWorkerMemory.workerMemoryEnv();
|
|
81
91
|
}
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
import { isJobQueueResolution, type JobHandler } from "../../types.js";
|
|
63
63
|
import { sweepOrphanConversationMemoryTables } from "./conversation-memory-orphan-sweep.js";
|
|
64
64
|
import { getLogger } from "./logging.js";
|
|
65
|
+
import { backfillRetrospectiveCursorTimestamps } from "./memory-retrospective-cursor-backfill.js";
|
|
65
66
|
import { sweepOrphanMemoryRetrospectiveConversations } from "./memory-retrospective-startup-cleanup.js";
|
|
66
67
|
import { getWorkspaceDir } from "./paths.js";
|
|
67
68
|
// SUBSTRATE (v2+v3) — feeds `enqueueSubstrateMaintenanceJobs`.
|
|
@@ -259,6 +260,18 @@ export function startMemoryJobsWorkerLoop(): MemoryJobsWorker {
|
|
|
259
260
|
);
|
|
260
261
|
});
|
|
261
262
|
|
|
263
|
+
// Second chance for the cursor timestamp backfill the daemon's memory init
|
|
264
|
+
// hook starts at boot: rows written before the column existed get their
|
|
265
|
+
// `createdAt` so a later deletion of the row they point at (a regenerated
|
|
266
|
+
// reply) cannot stall the conversation's retrospectives. Idempotent,
|
|
267
|
+
// detached, and best-effort, same as the sweeps.
|
|
268
|
+
void backfillRetrospectiveCursorTimestamps().catch((err: unknown) => {
|
|
269
|
+
log.warn(
|
|
270
|
+
{ err },
|
|
271
|
+
"Retrospective cursor timestamp backfill failed; continuing worker startup",
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
|
|
262
275
|
// Also catch up on relocated conversation-keyed tables orphaned while the
|
|
263
276
|
// plugin was disabled: with memory off, the conversation-deleted hook never
|
|
264
277
|
// fired, so deletes during that window left rows behind (the pre-Wave-2 main
|