@vellumai/assistant 0.12.0 → 0.12.1-dev.202609141913.5e16607
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 +5 -1
- package/ARCHITECTURE.md +11 -12
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/openapi.yaml +292 -50
- package/package.json +1 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- 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__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +17 -5
- 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__/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-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__/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__/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 +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- 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__/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__/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 +94 -9
- 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/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/index.ts +6 -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/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/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/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 +2 -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/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-process.ts +35 -64
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +10 -8
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-file-proxy.ts +8 -10
- 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/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 +119 -0
- package/src/desktop/desktop-chrome-policy.test.ts +71 -0
- package/src/desktop/desktop-chrome-policy.ts +42 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +306 -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 +627 -0
- package/src/desktop/desktop-session-manager.ts +814 -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-theme.ts +170 -0
- 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-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- 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/__tests__/seed-providers-managed.test.ts +54 -0
- package/src/oauth/byo-connection.test.ts +45 -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/seed-providers.ts +165 -0
- 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-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- 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-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/graph/capability-seed.ts +4 -6
- 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/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-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 +6 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
- 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/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/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- 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-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 +192 -78
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/runtime/AGENTS.md +2 -0
- 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__/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/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- 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/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 +55 -3
- 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/platform-routes.ts +18 -15
- 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/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/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/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/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +4 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- 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/policy-context.ts +6 -7
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/watch/watch-retro-report.ts +0 -8
- 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/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +5 -54
- 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
|
@@ -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", () => {
|
|
@@ -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,
|
|
@@ -131,12 +132,9 @@ export function seedSkillGraphNodes(): void {
|
|
|
131
132
|
const input = fromSkillSummary(summary);
|
|
132
133
|
|
|
133
134
|
if (summary.id === "mcp-setup") {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
if (names.length > 0) {
|
|
138
|
-
input.description += ` Configured: ${names.join(", ")}`;
|
|
139
|
-
}
|
|
135
|
+
const names = Object.keys(loadWorkspaceMcpConfig().servers);
|
|
136
|
+
if (names.length > 0) {
|
|
137
|
+
input.description += ` Configured: ${names.join(", ")}`;
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// accounting; once newer real messages arrive it is copied into the next
|
|
32
32
|
// run's fork as inert prefix context.
|
|
33
33
|
|
|
34
|
-
import { and, eq,
|
|
34
|
+
import { and, eq, like, or } from "drizzle-orm";
|
|
35
35
|
|
|
36
36
|
import {
|
|
37
37
|
countMessagesAfter,
|
|
@@ -43,6 +43,11 @@ import {
|
|
|
43
43
|
SIGHT_FRAME_ATTACHMENT_IDS_KEY,
|
|
44
44
|
} from "../../../persistence/conversation-types.js";
|
|
45
45
|
import { getDb } from "../../../persistence/db-connection.js";
|
|
46
|
+
import {
|
|
47
|
+
messagesAfterBoundFilter,
|
|
48
|
+
type MessagesAfterRef,
|
|
49
|
+
resolveMessagesAfterBound,
|
|
50
|
+
} from "../../../persistence/message-cursor.js";
|
|
46
51
|
import { messages } from "../../../persistence/schema/index.js";
|
|
47
52
|
import { SKILL_CARD_MESSAGE_KIND } from "./memory-retrospective-constants.js";
|
|
48
53
|
|
|
@@ -98,9 +103,9 @@ export function isSkillCardMessage(row: { metadata: string | null }): boolean {
|
|
|
98
103
|
*/
|
|
99
104
|
export function getRetrospectiveMessagesAfter(
|
|
100
105
|
conversationId: string,
|
|
101
|
-
|
|
106
|
+
after: MessagesAfterRef,
|
|
102
107
|
): MessageRow[] {
|
|
103
|
-
const rows = getMessagesAfter(conversationId,
|
|
108
|
+
const rows = getMessagesAfter(conversationId, after);
|
|
104
109
|
const firstUnfinalized = rows.findIndex((row) => row.finalized !== 1);
|
|
105
110
|
const bounded =
|
|
106
111
|
firstUnfinalized === -1 ? rows : rows.slice(0, firstUnfinalized);
|
|
@@ -116,13 +121,13 @@ export function getRetrospectiveMessagesAfter(
|
|
|
116
121
|
*/
|
|
117
122
|
export function countRetrospectiveMessagesAfter(
|
|
118
123
|
conversationId: string,
|
|
119
|
-
|
|
124
|
+
after: MessagesAfterRef,
|
|
120
125
|
): number {
|
|
121
|
-
const total = countMessagesAfter(conversationId,
|
|
126
|
+
const total = countMessagesAfter(conversationId, after);
|
|
122
127
|
if (total === 0) {
|
|
123
128
|
return 0;
|
|
124
129
|
}
|
|
125
|
-
const excluded = countExcludedMessagesAfter(conversationId,
|
|
130
|
+
const excluded = countExcludedMessagesAfter(conversationId, after);
|
|
126
131
|
return Math.max(0, total - excluded);
|
|
127
132
|
}
|
|
128
133
|
|
|
@@ -167,7 +172,8 @@ export function messagesHaveUserActivity(
|
|
|
167
172
|
* a non-tool_result block. Powers the `memory.retrospective.requireUserActivity`
|
|
168
173
|
* enqueue gate, so only user rows are loaded. Cursor semantics mirror
|
|
169
174
|
* `countMessagesAfter`: a null/`""` reference scans the whole conversation,
|
|
170
|
-
*
|
|
175
|
+
* a cursor whose row is gone falls back to the timestamp it carries, and a
|
|
176
|
+
* bare id whose row is gone means no new work.
|
|
171
177
|
*
|
|
172
178
|
* Operates on the raw `content` column: rows that do not parse to a block
|
|
173
179
|
* array (file-backed `{ ref }` rows, legacy plain strings) count as
|
|
@@ -177,37 +183,15 @@ export function messagesHaveUserActivity(
|
|
|
177
183
|
*/
|
|
178
184
|
export function hasQualifyingUserMessageAfter(
|
|
179
185
|
conversationId: string,
|
|
180
|
-
|
|
186
|
+
after: MessagesAfterRef,
|
|
181
187
|
): boolean {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
let ref: { createdAt: number } | undefined;
|
|
187
|
-
if (cursorId !== null) {
|
|
188
|
-
ref = db
|
|
189
|
-
.select({ createdAt: messages.createdAt })
|
|
190
|
-
.from(messages)
|
|
191
|
-
.where(eq(messages.id, cursorId))
|
|
192
|
-
.get();
|
|
193
|
-
if (!ref) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
188
|
+
const start = resolveMessagesAfterBound(after);
|
|
189
|
+
if (start.kind === "vanished") {
|
|
190
|
+
return false;
|
|
196
191
|
}
|
|
197
|
-
|
|
198
192
|
const afterCursor =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
or(
|
|
202
|
-
gt(messages.createdAt, ref.createdAt),
|
|
203
|
-
and(
|
|
204
|
-
eq(messages.createdAt, ref.createdAt),
|
|
205
|
-
gt(messages.id, cursorId),
|
|
206
|
-
),
|
|
207
|
-
),
|
|
208
|
-
]
|
|
209
|
-
: [];
|
|
210
|
-
const rows = db
|
|
193
|
+
start.kind === "after" ? [messagesAfterBoundFilter(start.bound)] : [];
|
|
194
|
+
const rows = getDb()
|
|
211
195
|
.select({ content: messages.content, metadata: messages.metadata })
|
|
212
196
|
.from(messages)
|
|
213
197
|
.where(
|
|
@@ -248,18 +232,14 @@ function rawUserContentCarriesActivity(raw: string | null): boolean {
|
|
|
248
232
|
* — at most one per successful retrospective run); each candidate is then
|
|
249
233
|
* JSON-verified by {@link isSkillCardMessage} so an incidental substring
|
|
250
234
|
* match in unrelated metadata can never exclude a real message from
|
|
251
|
-
* accounting. The cursor
|
|
252
|
-
*
|
|
253
|
-
*
|
|
235
|
+
* accounting. The cursor resolves exactly as `countMessagesAfter`'s does,
|
|
236
|
+
* including the null/`""` "count everything" cases and the vanished-reference
|
|
237
|
+
* "no new work" case.
|
|
254
238
|
*/
|
|
255
239
|
function countExcludedMessagesAfter(
|
|
256
240
|
conversationId: string,
|
|
257
|
-
|
|
241
|
+
after: MessagesAfterRef,
|
|
258
242
|
): number {
|
|
259
|
-
// The `""` sentinel (failure-only state rows) counts everything, matching
|
|
260
|
-
// `countMessagesAfter`.
|
|
261
|
-
const cursorId =
|
|
262
|
-
afterMessageId === null || afterMessageId === "" ? null : afterMessageId;
|
|
263
243
|
const db = getDb();
|
|
264
244
|
const candidates = db
|
|
265
245
|
.select({
|
|
@@ -282,18 +262,11 @@ function countExcludedMessagesAfter(
|
|
|
282
262
|
return 0;
|
|
283
263
|
}
|
|
284
264
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.where(eq(messages.id, cursorId))
|
|
291
|
-
.get();
|
|
292
|
-
// Vanished reference: `countMessagesAfter` reported 0, so there is
|
|
293
|
-
// nothing to subtract from.
|
|
294
|
-
if (!ref) {
|
|
295
|
-
return 0;
|
|
296
|
-
}
|
|
265
|
+
const start = resolveMessagesAfterBound(after);
|
|
266
|
+
// Vanished reference: `countMessagesAfter` reported 0, so there is nothing
|
|
267
|
+
// to subtract from.
|
|
268
|
+
if (start.kind === "vanished") {
|
|
269
|
+
return 0;
|
|
297
270
|
}
|
|
298
271
|
|
|
299
272
|
let count = 0;
|
|
@@ -301,11 +274,12 @@ function countExcludedMessagesAfter(
|
|
|
301
274
|
if (!isExcludedFromRetrospectiveAccounting(row)) {
|
|
302
275
|
continue;
|
|
303
276
|
}
|
|
304
|
-
if (
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
277
|
+
if (start.kind === "after") {
|
|
278
|
+
const { createdAt, id } = start.bound;
|
|
279
|
+
const isAfter =
|
|
280
|
+
row.createdAt > createdAt ||
|
|
281
|
+
(row.createdAt === createdAt && row.id > id);
|
|
282
|
+
if (!isAfter) {
|
|
309
283
|
continue;
|
|
310
284
|
}
|
|
311
285
|
}
|