@vellumai/assistant 0.5.16 → 0.6.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 +69 -16
- package/Dockerfile +2 -5
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +32 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/knip.json +2 -1
- package/openapi.yaml +1198 -83
- package/package.json +5 -1
- package/src/__tests__/actor-token-service.test.ts +68 -0
- package/src/__tests__/agent-loop.test.ts +0 -32
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +217 -98
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/call-conversation-messages.test.ts +2 -6
- package/src/__tests__/call-domain.test.ts +2 -6
- package/src/__tests__/call-pointer-messages.test.ts +2 -14
- package/src/__tests__/call-recovery.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +2 -6
- package/src/__tests__/call-store.test.ts +2 -6
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
- package/src/__tests__/canonical-guardian-store.test.ts +2 -6
- package/src/__tests__/channel-delivery-store.test.ts +2 -6
- package/src/__tests__/channel-retry-sweep.test.ts +2 -6
- package/src/__tests__/checker.test.ts +63 -9
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
- package/src/__tests__/config-schema.test.ts +6 -1
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
- package/src/__tests__/contacts-tools.test.ts +31 -0
- package/src/__tests__/context-overflow-reducer.test.ts +86 -0
- package/src/__tests__/context-token-estimator.test.ts +175 -10
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +13 -6
- package/src/__tests__/conversation-agent-loop.test.ts +13 -51
- package/src/__tests__/conversation-attachments.test.ts +2 -6
- package/src/__tests__/conversation-attention-store.test.ts +2 -6
- package/src/__tests__/conversation-clear-safety.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
- package/src/__tests__/conversation-disk-view.test.ts +2 -6
- package/src/__tests__/conversation-error.test.ts +33 -2
- package/src/__tests__/conversation-fork-crud.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -1
- package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
- package/src/__tests__/conversation-media-retry.test.ts +91 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-usage.test.ts +2 -6
- package/src/__tests__/conversation-wipe.test.ts +13 -414
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +2 -0
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/followup-tools.test.ts +2 -6
- package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
- package/src/__tests__/guardian-action-store.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
- package/src/__tests__/guardian-dispatch.test.ts +2 -6
- package/src/__tests__/guardian-grant-minting.test.ts +2 -14
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
- package/src/__tests__/guardian-routing-state.test.ts +2 -6
- package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +178 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +293 -0
- package/src/__tests__/invite-redemption-service.test.ts +2 -6
- package/src/__tests__/invite-routes-http.test.ts +2 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +17 -28
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +103 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +164 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +74 -111
- package/src/__tests__/managed-store.test.ts +38 -11
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +134 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/notification-guardian-path.test.ts +2 -6
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
- package/src/__tests__/onboarding-template-contract.test.ts +62 -14
- package/src/__tests__/outlook-attachments.test.ts +301 -0
- package/src/__tests__/outlook-automation-tools.test.ts +425 -0
- package/src/__tests__/outlook-categories.test.ts +212 -0
- package/src/__tests__/outlook-client-automation.test.ts +246 -0
- package/src/__tests__/outlook-compose-tools.test.ts +325 -0
- package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
- package/src/__tests__/outlook-email-watcher.test.ts +322 -0
- package/src/__tests__/outlook-follow-up.test.ts +196 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/platform-callback-registration.test.ts +4 -4
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/provider-error-scenarios.test.ts +21 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +3 -3
- package/src/__tests__/require-fresh-approval.test.ts +64 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/schedule-store.test.ts +2 -6
- package/src/__tests__/schedule-tools.test.ts +2 -6
- package/src/__tests__/scheduler-recurrence.test.ts +1 -5
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +422 -0
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +44 -9
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +23 -11
- package/src/__tests__/skill-memory.test.ts +2 -741
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-compiler.test.ts +2 -6
- package/src/__tests__/task-management-tools.test.ts +2 -6
- package/src/__tests__/task-memory-cleanup.test.ts +185 -241
- package/src/__tests__/task-runner.test.ts +2 -6
- package/src/__tests__/task-scheduler.test.ts +2 -6
- package/src/__tests__/terminal-tools.test.ts +17 -27
- package/src/__tests__/test-preload.ts +7 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -6
- package/src/__tests__/tool-executor.test.ts +4 -26
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +277 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/trust-store.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +118 -8
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +2 -6
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
- package/src/__tests__/usage-routes.test.ts +2 -6
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -6
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
- package/src/__tests__/voice-session-bridge.test.ts +2 -6
- package/src/__tests__/volume-security-guard.test.ts +2 -0
- package/src/__tests__/workspace-lifecycle.test.ts +29 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/agent/attachments.ts +7 -2
- package/src/agent/image-optimize.ts +165 -0
- package/src/agent/loop.ts +7 -15
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/bundler/app-compiler.ts +179 -2
- package/src/bundler/package-resolver.ts +3 -5
- package/src/cli/__tests__/notifications.test.ts +1 -2
- package/src/cli/__tests__/run-assistant-command.ts +29 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +37 -84
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +25 -11
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
- package/src/cli/commands/platform/index.ts +16 -16
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +218 -36
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/cli/program.ts +2 -0
- package/src/cli.ts +1 -120
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
- package/src/config/bundled-skills/contacts/SKILL.md +0 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
- package/src/config/bundled-skills/gmail/SKILL.md +4 -12
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +17 -18
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
- package/src/config/bundled-skills/outlook/SKILL.md +189 -0
- package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
- package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
- package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/slack/SKILL.md +3 -7
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +78 -8
- package/src/config/feature-flag-registry.json +38 -125
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/config/skills.ts +18 -7
- package/src/context/token-estimator.ts +25 -18
- package/src/context/window-manager.ts +6 -2
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +79 -1
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +143 -82
- package/src/daemon/conversation-agent-loop.ts +236 -108
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +36 -9
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-process.ts +13 -7
- package/src/daemon/conversation-runtime-assembly.ts +305 -306
- package/src/daemon/conversation-tool-setup.ts +44 -14
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +59 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +4 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +85 -17
- package/src/daemon/handlers/skills.ts +416 -209
- package/src/daemon/lifecycle.ts +212 -131
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +29 -7
- package/src/daemon/message-types/messages.ts +12 -2
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +6 -0
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/providers-setup.ts +5 -0
- package/src/daemon/server.ts +100 -11
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +50 -8
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +97 -7
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/runner.ts +15 -38
- package/src/inbound/platform-callback-registration.ts +14 -14
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +42 -75
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +3 -1
- package/src/memory/conversation-crud.ts +211 -288
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +61 -13
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +194 -361
- package/src/memory/embed.ts +73 -0
- package/src/memory/embedding-backend.ts +8 -14
- package/src/memory/embedding-runtime-manager.ts +12 -114
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/graph/bootstrap.ts +521 -0
- package/src/memory/graph/capability-seed.ts +449 -0
- package/src/memory/graph/consolidation.ts +725 -0
- package/src/memory/graph/conversation-graph-memory.ts +659 -0
- package/src/memory/graph/decay.test.ts +208 -0
- package/src/memory/graph/decay.ts +195 -0
- package/src/memory/graph/extraction-job.ts +74 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1297 -0
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +280 -0
- package/src/memory/graph/image-ref-utils.ts +29 -0
- package/src/memory/graph/injection.test.ts +513 -0
- package/src/memory/graph/injection.ts +469 -0
- package/src/memory/graph/inspect.ts +543 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1111 -0
- package/src/memory/graph/scoring.test.ts +548 -0
- package/src/memory/graph/scoring.ts +232 -0
- package/src/memory/graph/serendipity.ts +65 -0
- package/src/memory/graph/store.test.ts +1098 -0
- package/src/memory/graph/store.ts +838 -0
- package/src/memory/graph/tool-handlers.ts +301 -0
- package/src/memory/graph/tools.ts +97 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +295 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +32 -42
- package/src/memory/job-handlers/conversation-starters.ts +91 -53
- package/src/memory/job-handlers/embedding.ts +5 -31
- package/src/memory/job-handlers/index-maintenance.ts +23 -11
- package/src/memory/job-handlers/summarization.ts +32 -17
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +21 -31
- package/src/memory/jobs-worker.ts +180 -129
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
- package/src/memory/migrations/203-drop-memory-items-tables.ts +55 -0
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
- package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +12 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +154 -0
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/task-memory-cleanup.ts +58 -61
- package/src/messaging/providers/outlook/adapter.ts +8 -1
- package/src/messaging/providers/outlook/client.ts +299 -0
- package/src/messaging/providers/outlook/types.ts +118 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/copy-composer.ts +95 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/seed-providers.ts +2 -1
- package/src/permissions/checker.ts +36 -4
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/workspace-policy.ts +10 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +62 -36
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +70 -165
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +25 -4
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +136 -220
- package/src/providers/gemini/client.ts +1 -1
- package/src/providers/openai/client.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +19 -3
- package/src/runtime/actor-trust-resolver.ts +5 -1
- package/src/runtime/auth/route-policy.ts +30 -0
- package/src/runtime/guardian-reply-router.ts +5 -1
- package/src/runtime/http-server.ts +55 -5
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +22 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
- package/src/runtime/routes/conversation-management-routes.ts +3 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +264 -44
- package/src/runtime/routes/conversation-starter-routes.ts +2 -2
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/global-search-routes.ts +21 -19
- package/src/runtime/routes/group-routes.ts +207 -0
- package/src/runtime/routes/guardian-action-routes.ts +21 -10
- package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/inbound-message-handler.ts +19 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export-routes.ts +23 -275
- package/src/runtime/routes/memory-item-routes.test.ts +170 -247
- package/src/runtime/routes/memory-item-routes.ts +341 -388
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +28 -11
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/work-items-routes.test.ts +2 -6
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +52 -18
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +4 -8
- package/src/shared/provider-env-vars.ts +19 -0
- package/src/skills/catalog-cache.ts +5 -0
- package/src/skills/catalog-install.ts +25 -16
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +29 -18
- package/src/skills/skill-memory.ts +12 -229
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +7 -5
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +85 -1
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +17 -1
- package/src/tools/tool-manifest.ts +9 -3
- package/src/tools/types.ts +2 -0
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
- package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
- package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +84 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -372
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/__tests__/context-memory-e2e.test.ts +0 -415
- package/src/__tests__/journal-context.test.ts +0 -268
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
- package/src/__tests__/memory-query-builder.test.ts +0 -59
- package/src/__tests__/memory-recall-quality.test.ts +0 -1046
- package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
- package/src/__tests__/memory-regressions.test.ts +0 -3696
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
- package/src/cli/cli-memory.ts +0 -176
- package/src/daemon/conversation-memory.ts +0 -207
- package/src/memory/conversation-starters-cadence.ts +0 -74
- package/src/memory/items-extractor.ts +0 -860
- package/src/memory/job-handlers/batch-extraction.ts +0 -753
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
- package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
- package/src/memory/journal-memory.ts +0 -224
- package/src/memory/query-builder.ts +0 -47
- package/src/memory/query-expansion.ts +0 -83
- package/src/memory/retriever.test.ts +0 -1592
- package/src/memory/retriever.ts +0 -1331
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -139
- package/src/memory/search/ranking.ts +0 -15
- package/src/memory/search/staleness.ts +0 -40
- package/src/memory/search/tier-classifier.ts +0 -18
- package/src/memory/search/types.ts +0 -121
- package/src/prompts/journal-context.ts +0 -154
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -562
- package/src/tools/memory/handlers.ts +0 -434
- package/src/util/clipboard.ts +0 -34
|
@@ -41,17 +41,15 @@ import {
|
|
|
41
41
|
updateMetaFile,
|
|
42
42
|
} from "./conversation-disk-view.js";
|
|
43
43
|
import { ensureDisplayOrderMigration } from "./conversation-display-order-migration.js";
|
|
44
|
-
import {
|
|
44
|
+
import { ensureGroupMigration } from "./conversation-group-migration.js";
|
|
45
|
+
import { getDb, rawExec, rawGet, rawRun } from "./db.js";
|
|
45
46
|
import { indexMessageNow } from "./indexer.js";
|
|
46
|
-
import { enqueueMemoryJob } from "./jobs-store.js";
|
|
47
47
|
import {
|
|
48
48
|
channelInboundEvents,
|
|
49
49
|
conversations,
|
|
50
50
|
conversationStarters,
|
|
51
51
|
llmRequestLogs,
|
|
52
52
|
memoryEmbeddings,
|
|
53
|
-
memoryItems,
|
|
54
|
-
memoryItemSources,
|
|
55
53
|
memorySegments,
|
|
56
54
|
memorySummaries,
|
|
57
55
|
messageAttachments,
|
|
@@ -72,7 +70,7 @@ const interfaceIdSchema = z.enum(INTERFACE_IDS);
|
|
|
72
70
|
const subagentNotificationSchema = z.object({
|
|
73
71
|
subagentId: z.string(),
|
|
74
72
|
label: z.string(),
|
|
75
|
-
status: z.enum(["completed", "failed", "aborted"]),
|
|
73
|
+
status: z.enum(["running", "completed", "failed", "aborted"]),
|
|
76
74
|
error: z.string().optional(),
|
|
77
75
|
conversationId: z.string().optional(),
|
|
78
76
|
});
|
|
@@ -174,6 +172,7 @@ export interface ConversationRow {
|
|
|
174
172
|
forkParentMessageId: string | null;
|
|
175
173
|
isAutoTitle: number;
|
|
176
174
|
scheduleJobId: string | null;
|
|
175
|
+
lastMessageAt: number | null;
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
export const parseConversation = createRowMapper<
|
|
@@ -199,6 +198,7 @@ export const parseConversation = createRowMapper<
|
|
|
199
198
|
forkParentMessageId: "forkParentMessageId",
|
|
200
199
|
isAutoTitle: "isAutoTitle",
|
|
201
200
|
scheduleJobId: "scheduleJobId",
|
|
201
|
+
lastMessageAt: "lastMessageAt",
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
export interface MessageRow {
|
|
@@ -241,9 +241,10 @@ export function createConversation(
|
|
|
241
241
|
| string
|
|
242
242
|
| {
|
|
243
243
|
title?: string;
|
|
244
|
-
conversationType?: "standard" | "private" | "background";
|
|
244
|
+
conversationType?: "standard" | "private" | "background" | "scheduled";
|
|
245
245
|
source?: string;
|
|
246
246
|
scheduleJobId?: string;
|
|
247
|
+
groupId?: string;
|
|
247
248
|
},
|
|
248
249
|
) {
|
|
249
250
|
const db = getDb();
|
|
@@ -254,9 +255,16 @@ export function createConversation(
|
|
|
254
255
|
: (titleOrOpts ?? {});
|
|
255
256
|
const conversationType = opts.conversationType ?? "standard";
|
|
256
257
|
const source = opts.source ?? "user";
|
|
258
|
+
const groupId = opts.groupId;
|
|
257
259
|
const id = uuid();
|
|
258
260
|
const memoryScopeId =
|
|
259
261
|
conversationType === "private" ? `private:${id}` : "default";
|
|
262
|
+
|
|
263
|
+
// Ensure group_id column exists for deterministic schema readiness,
|
|
264
|
+
// even when this conversation has no groupId (a subsequent query or
|
|
265
|
+
// reorder may reference the column).
|
|
266
|
+
ensureGroupMigration();
|
|
267
|
+
|
|
260
268
|
const conversation = {
|
|
261
269
|
id,
|
|
262
270
|
title: opts.title ?? null,
|
|
@@ -276,6 +284,12 @@ export function createConversation(
|
|
|
276
284
|
|
|
277
285
|
// Retry on SQLITE_BUSY and SQLITE_IOERR — transient disk I/O errors or WAL
|
|
278
286
|
// contention can cause the first attempt to fail even under normal load.
|
|
287
|
+
// INSERT and group_id UPDATE are retried independently so a transient failure
|
|
288
|
+
// on the UPDATE doesn't re-execute the already-succeeded INSERT (which would
|
|
289
|
+
// hit a unique constraint violation).
|
|
290
|
+
// No explicit BEGIN/COMMIT here — callers that need atomicity (e.g.
|
|
291
|
+
// forkConversation) wrap in their own transaction, and nesting raw BEGIN
|
|
292
|
+
// inside Drizzle's db.transaction() would crash SQLite.
|
|
279
293
|
const MAX_RETRIES = 3;
|
|
280
294
|
for (let attempt = 0; ; attempt++) {
|
|
281
295
|
try {
|
|
@@ -289,10 +303,8 @@ export function createConversation(
|
|
|
289
303
|
) {
|
|
290
304
|
log.warn(
|
|
291
305
|
{ attempt, conversationId: id, code },
|
|
292
|
-
"createConversation: transient
|
|
306
|
+
"createConversation: INSERT transient error, retrying",
|
|
293
307
|
);
|
|
294
|
-
// Synchronous sleep — createConversation is synchronous and the
|
|
295
|
-
// retry window is short (50-150ms), so Bun.sleepSync is appropriate.
|
|
296
308
|
Bun.sleepSync(50 * (attempt + 1));
|
|
297
309
|
continue;
|
|
298
310
|
}
|
|
@@ -300,6 +312,35 @@ export function createConversation(
|
|
|
300
312
|
}
|
|
301
313
|
}
|
|
302
314
|
|
|
315
|
+
// group_id is NOT in the Drizzle schema (raw-query-only pattern).
|
|
316
|
+
// Set via raw SQL after the INSERT succeeds.
|
|
317
|
+
if (groupId) {
|
|
318
|
+
for (let attempt = 0; ; attempt++) {
|
|
319
|
+
try {
|
|
320
|
+
rawRun(
|
|
321
|
+
"UPDATE conversations SET group_id = ? WHERE id = ?",
|
|
322
|
+
groupId,
|
|
323
|
+
id,
|
|
324
|
+
);
|
|
325
|
+
break;
|
|
326
|
+
} catch (err) {
|
|
327
|
+
const code = (err as { code?: string }).code ?? "";
|
|
328
|
+
if (
|
|
329
|
+
attempt < MAX_RETRIES &&
|
|
330
|
+
(code.startsWith("SQLITE_BUSY") || code.startsWith("SQLITE_IOERR"))
|
|
331
|
+
) {
|
|
332
|
+
log.warn(
|
|
333
|
+
{ attempt, conversationId: id, code },
|
|
334
|
+
"createConversation: group_id UPDATE transient error, retrying",
|
|
335
|
+
);
|
|
336
|
+
Bun.sleepSync(50 * (attempt + 1));
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
303
344
|
initConversationDir({ ...conversation, originChannel: null });
|
|
304
345
|
|
|
305
346
|
return conversation;
|
|
@@ -344,6 +385,20 @@ export function getConversationMemoryScopeId(conversationId: string): string {
|
|
|
344
385
|
return conv?.memoryScopeId ?? "default";
|
|
345
386
|
}
|
|
346
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Fetch group_id for a conversation via raw SQL. group_id is NOT in the
|
|
390
|
+
* Drizzle schema (raw-query-only pattern), so ConversationRow doesn't
|
|
391
|
+
* include it. This helper is used by forkConversation to inherit group_id.
|
|
392
|
+
*/
|
|
393
|
+
export function getConversationGroupId(conversationId: string): string | null {
|
|
394
|
+
ensureGroupMigration();
|
|
395
|
+
const row = rawGet<{ group_id: string | null }>(
|
|
396
|
+
"SELECT group_id FROM conversations WHERE id = ?",
|
|
397
|
+
conversationId,
|
|
398
|
+
);
|
|
399
|
+
return row?.group_id ?? null;
|
|
400
|
+
}
|
|
401
|
+
|
|
347
402
|
export function forkConversation(params: {
|
|
348
403
|
conversationId: string;
|
|
349
404
|
throughMessageId?: string;
|
|
@@ -407,10 +462,14 @@ export function forkConversation(params: {
|
|
|
407
462
|
// (linkAttachmentToMessage, relinkAttachments, seedForkedConversationAttention)
|
|
408
463
|
// use the same underlying bun:sqlite connection, so their writes participate
|
|
409
464
|
// in this transaction automatically.
|
|
465
|
+
// Inherit group_id from parent via raw SQL helper (group_id is not in Drizzle schema)
|
|
466
|
+
const parentGroupId = getConversationGroupId(conversationId);
|
|
467
|
+
|
|
410
468
|
const forkedConversation = db.transaction(() => {
|
|
411
469
|
const fc = createConversation({
|
|
412
470
|
title: forkTitle,
|
|
413
471
|
conversationType: "standard",
|
|
472
|
+
groupId: parentGroupId ?? undefined,
|
|
414
473
|
});
|
|
415
474
|
|
|
416
475
|
db.update(conversations)
|
|
@@ -526,6 +585,16 @@ export function forkConversation(params: {
|
|
|
526
585
|
});
|
|
527
586
|
}
|
|
528
587
|
|
|
588
|
+
// Set lastMessageAt to the max createdAt of copied messages so the
|
|
589
|
+
// forked conversation sorts correctly by message recency.
|
|
590
|
+
const lastCopiedMessage = messagesToCopy.at(-1);
|
|
591
|
+
if (lastCopiedMessage) {
|
|
592
|
+
db.update(conversations)
|
|
593
|
+
.set({ lastMessageAt: lastCopiedMessage.createdAt })
|
|
594
|
+
.where(eq(conversations.id, fc.id))
|
|
595
|
+
.run();
|
|
596
|
+
}
|
|
597
|
+
|
|
529
598
|
seedForkedConversationAttention({
|
|
530
599
|
conversationId: fc.id,
|
|
531
600
|
latestAssistantMessageId: latestForkedAssistant?.messageId ?? null,
|
|
@@ -553,14 +622,13 @@ export function forkConversation(params: {
|
|
|
553
622
|
|
|
554
623
|
/**
|
|
555
624
|
* Delete a conversation and all its messages, cleaning up orphaned memory
|
|
556
|
-
* artifacts (
|
|
557
|
-
*
|
|
625
|
+
* artifacts (embeddings). Returns segment IDs so callers can clean up
|
|
626
|
+
* the corresponding Qdrant vector entries.
|
|
558
627
|
*/
|
|
559
628
|
export function deleteConversation(id: string): DeletedMemoryIds {
|
|
560
629
|
const db = getDb();
|
|
561
630
|
const result: DeletedMemoryIds = {
|
|
562
631
|
segmentIds: [],
|
|
563
|
-
orphanedItemIds: [],
|
|
564
632
|
deletedSummaryIds: [],
|
|
565
633
|
};
|
|
566
634
|
|
|
@@ -589,16 +657,6 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
589
657
|
.all();
|
|
590
658
|
result.segmentIds = linkedSegments.map((r) => r.id);
|
|
591
659
|
|
|
592
|
-
// Collect memory item IDs linked to these messages before cascade.
|
|
593
|
-
const linkedItems = tx
|
|
594
|
-
.select({ memoryItemId: memoryItemSources.memoryItemId })
|
|
595
|
-
.from(memoryItemSources)
|
|
596
|
-
.where(inArray(memoryItemSources.messageId, messageIds))
|
|
597
|
-
.all();
|
|
598
|
-
const candidateItemIds = [
|
|
599
|
-
...new Set(linkedItems.map((r) => r.memoryItemId)),
|
|
600
|
-
];
|
|
601
|
-
|
|
602
660
|
// Delete non-cascading tables first.
|
|
603
661
|
tx.delete(llmRequestLogs)
|
|
604
662
|
.where(eq(llmRequestLogs.conversationId, id))
|
|
@@ -606,7 +664,7 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
606
664
|
tx.delete(toolInvocations)
|
|
607
665
|
.where(eq(toolInvocations.conversationId, id))
|
|
608
666
|
.run();
|
|
609
|
-
// Cascade deletes memory_segments,
|
|
667
|
+
// Cascade deletes memory_segments, message_attachments.
|
|
610
668
|
tx.delete(messages).where(eq(messages.conversationId, id)).run();
|
|
611
669
|
|
|
612
670
|
// Clean up segment embeddings.
|
|
@@ -620,34 +678,6 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
620
678
|
)
|
|
621
679
|
.run();
|
|
622
680
|
}
|
|
623
|
-
|
|
624
|
-
// Clean up orphaned memory items whose only sources were in this conversation.
|
|
625
|
-
if (candidateItemIds.length > 0) {
|
|
626
|
-
const surviving = tx
|
|
627
|
-
.select({ memoryItemId: memoryItemSources.memoryItemId })
|
|
628
|
-
.from(memoryItemSources)
|
|
629
|
-
.where(inArray(memoryItemSources.memoryItemId, candidateItemIds))
|
|
630
|
-
.all();
|
|
631
|
-
const survivingIds = new Set(surviving.map((r) => r.memoryItemId));
|
|
632
|
-
const orphanedIds = candidateItemIds.filter(
|
|
633
|
-
(itemId) => !survivingIds.has(itemId),
|
|
634
|
-
);
|
|
635
|
-
result.orphanedItemIds = orphanedIds;
|
|
636
|
-
|
|
637
|
-
if (orphanedIds.length > 0) {
|
|
638
|
-
tx.delete(memoryEmbeddings)
|
|
639
|
-
.where(
|
|
640
|
-
and(
|
|
641
|
-
eq(memoryEmbeddings.targetType, "item"),
|
|
642
|
-
inArray(memoryEmbeddings.targetId, orphanedIds),
|
|
643
|
-
),
|
|
644
|
-
)
|
|
645
|
-
.run();
|
|
646
|
-
tx.delete(memoryItems)
|
|
647
|
-
.where(inArray(memoryItems.id, orphanedIds))
|
|
648
|
-
.run();
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
681
|
} else {
|
|
652
682
|
// No messages — just clean up non-message tables.
|
|
653
683
|
tx.delete(llmRequestLogs)
|
|
@@ -659,35 +689,6 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
659
689
|
}
|
|
660
690
|
|
|
661
691
|
if (isPrivateScope && memoryScopeId) {
|
|
662
|
-
// Sweep remaining memory items with this private scopeId.
|
|
663
|
-
const scopeItems = tx
|
|
664
|
-
.select({ id: memoryItems.id })
|
|
665
|
-
.from(memoryItems)
|
|
666
|
-
.where(eq(memoryItems.scopeId, memoryScopeId))
|
|
667
|
-
.all();
|
|
668
|
-
const alreadyDeleted = new Set(result.orphanedItemIds);
|
|
669
|
-
const scopeItemIds = scopeItems
|
|
670
|
-
.map((r) => r.id)
|
|
671
|
-
.filter((id) => !alreadyDeleted.has(id));
|
|
672
|
-
|
|
673
|
-
if (scopeItemIds.length > 0) {
|
|
674
|
-
tx.delete(memoryEmbeddings)
|
|
675
|
-
.where(
|
|
676
|
-
and(
|
|
677
|
-
eq(memoryEmbeddings.targetType, "item"),
|
|
678
|
-
inArray(memoryEmbeddings.targetId, scopeItemIds),
|
|
679
|
-
),
|
|
680
|
-
)
|
|
681
|
-
.run();
|
|
682
|
-
tx.delete(memoryItemSources)
|
|
683
|
-
.where(inArray(memoryItemSources.memoryItemId, scopeItemIds))
|
|
684
|
-
.run();
|
|
685
|
-
tx.delete(memoryItems)
|
|
686
|
-
.where(inArray(memoryItems.id, scopeItemIds))
|
|
687
|
-
.run();
|
|
688
|
-
result.orphanedItemIds.push(...scopeItemIds);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
692
|
// Sweep memory summaries with this private scopeId.
|
|
692
693
|
const scopeSummaries = tx
|
|
693
694
|
.select({ id: memorySummaries.id })
|
|
@@ -733,79 +734,16 @@ export function deleteConversation(id: string): DeletedMemoryIds {
|
|
|
733
734
|
*
|
|
734
735
|
* Extends `deleteConversation` with:
|
|
735
736
|
* - Cancelling pending memory jobs before deletion
|
|
736
|
-
* - Restoring memory items that were explicitly superseded by items from this conversation
|
|
737
|
-
* - Restoring orphaned subject-match superseded items after deletion
|
|
738
737
|
* - Deleting conversation-scoped memory summaries and their embeddings
|
|
739
|
-
* - Enqueuing `embed_item` jobs for all restored items
|
|
740
738
|
*/
|
|
741
739
|
export function wipeConversation(id: string): WipeConversationResult {
|
|
742
740
|
const db = getDb();
|
|
743
|
-
const unsupersededItemIds: string[] = [];
|
|
744
741
|
const deletedSummaryIds: string[] = [];
|
|
745
742
|
|
|
746
743
|
// Step A — Cancel pending memory jobs (before deleting messages, since
|
|
747
744
|
// the cancellation queries join on `messages`).
|
|
748
745
|
const cancelledJobCount = cancelPendingJobsForConversation(id);
|
|
749
746
|
|
|
750
|
-
// Step B — Un-supersede memory items with explicit `supersededBy` links.
|
|
751
|
-
// Find memory items whose `superseded_by` points to an item sourced
|
|
752
|
-
// exclusively from this conversation.
|
|
753
|
-
const explicitSuperseded = rawAll<{ oldItemId: string }>(
|
|
754
|
-
`SELECT DISTINCT mi_old.id AS oldItemId
|
|
755
|
-
FROM memory_items mi_old
|
|
756
|
-
JOIN memory_items mi_new ON mi_old.superseded_by = mi_new.id
|
|
757
|
-
WHERE mi_old.status = 'superseded'
|
|
758
|
-
AND mi_new.id IN (
|
|
759
|
-
SELECT mis.memory_item_id
|
|
760
|
-
FROM memory_item_sources mis
|
|
761
|
-
JOIN messages m ON m.id = mis.message_id
|
|
762
|
-
WHERE m.conversation_id = ?
|
|
763
|
-
)
|
|
764
|
-
AND NOT EXISTS (
|
|
765
|
-
SELECT 1 FROM memory_item_sources mis2
|
|
766
|
-
JOIN messages m2 ON m2.id = mis2.message_id
|
|
767
|
-
WHERE mis2.memory_item_id = mi_new.id
|
|
768
|
-
AND m2.conversation_id != ?
|
|
769
|
-
)
|
|
770
|
-
AND NOT EXISTS (
|
|
771
|
-
SELECT 1 FROM memory_items mi_active
|
|
772
|
-
WHERE mi_active.kind = mi_old.kind
|
|
773
|
-
AND mi_active.subject = mi_old.subject
|
|
774
|
-
AND mi_active.scope_id = mi_old.scope_id
|
|
775
|
-
AND mi_active.status = 'active'
|
|
776
|
-
AND mi_active.id != mi_old.id
|
|
777
|
-
-- Exclude items sourced exclusively from the conversation being
|
|
778
|
-
-- wiped — deleteConversation will remove them, so they should not
|
|
779
|
-
-- block restoration of mi_old.
|
|
780
|
-
AND NOT (
|
|
781
|
-
EXISTS (
|
|
782
|
-
SELECT 1 FROM memory_item_sources mis_a
|
|
783
|
-
JOIN messages m_a ON m_a.id = mis_a.message_id
|
|
784
|
-
WHERE mis_a.memory_item_id = mi_active.id
|
|
785
|
-
AND m_a.conversation_id = ?
|
|
786
|
-
)
|
|
787
|
-
AND NOT EXISTS (
|
|
788
|
-
SELECT 1 FROM memory_item_sources mis_b
|
|
789
|
-
JOIN messages m_b ON m_b.id = mis_b.message_id
|
|
790
|
-
WHERE mis_b.memory_item_id = mi_active.id
|
|
791
|
-
AND m_b.conversation_id != ?
|
|
792
|
-
)
|
|
793
|
-
)
|
|
794
|
-
)`,
|
|
795
|
-
id,
|
|
796
|
-
id,
|
|
797
|
-
id,
|
|
798
|
-
id,
|
|
799
|
-
);
|
|
800
|
-
for (const { oldItemId } of explicitSuperseded) {
|
|
801
|
-
rawRun(
|
|
802
|
-
"UPDATE memory_items SET status = 'active', superseded_by = NULL WHERE id = ?",
|
|
803
|
-
oldItemId,
|
|
804
|
-
);
|
|
805
|
-
enqueueMemoryJob("embed_item", { itemId: oldItemId });
|
|
806
|
-
unsupersededItemIds.push(oldItemId);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
747
|
// Step C — Delete conversation-scoped memory summaries and their embeddings.
|
|
810
748
|
const summaryRows = db
|
|
811
749
|
.select({ id: memorySummaries.id })
|
|
@@ -833,82 +771,14 @@ export function wipeConversation(id: string): WipeConversationResult {
|
|
|
833
771
|
}
|
|
834
772
|
deletedSummaryIds.push(...summaryIds);
|
|
835
773
|
|
|
836
|
-
// Step D —
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
// Step D.5 — Collect kind + subject pairs of items that will be orphaned
|
|
840
|
-
// by deleteConversation. These are items sourced from this conversation's
|
|
841
|
-
// messages that have NO sources from any other conversation. We need this
|
|
842
|
-
// before deletion so we can scope Step F to only restore superseded items
|
|
843
|
-
// matching the specific kind + subject pairs that just lost their active
|
|
844
|
-
// replacement.
|
|
845
|
-
const orphanedKindSubjects = rawAll<{ kind: string; subject: string }>(
|
|
846
|
-
`SELECT DISTINCT mi.kind, mi.subject
|
|
847
|
-
FROM memory_items mi
|
|
848
|
-
JOIN memory_item_sources mis ON mis.memory_item_id = mi.id
|
|
849
|
-
JOIN messages m ON m.id = mis.message_id
|
|
850
|
-
WHERE m.conversation_id = ?
|
|
851
|
-
AND NOT EXISTS (
|
|
852
|
-
SELECT 1 FROM memory_item_sources mis2
|
|
853
|
-
JOIN messages m2 ON m2.id = mis2.message_id
|
|
854
|
-
WHERE mis2.memory_item_id = mi.id
|
|
855
|
-
AND m2.conversation_id != ?
|
|
856
|
-
)`,
|
|
857
|
-
id,
|
|
858
|
-
id,
|
|
859
|
-
);
|
|
860
|
-
|
|
861
|
-
// Step E — Delegate to deleteConversation which handles messages (cascade
|
|
862
|
-
// segments, item_sources, attachments), llmRequestLogs, toolInvocations,
|
|
863
|
-
// orphaned memory items + embeddings, and the conversation row.
|
|
774
|
+
// Step D — Delegate to deleteConversation which handles messages (cascade
|
|
775
|
+
// segments, attachments), llmRequestLogs, toolInvocations,
|
|
776
|
+
// embeddings, and the conversation row.
|
|
864
777
|
const deletedMemoryIds = deleteConversation(id);
|
|
865
778
|
|
|
866
|
-
// Step
|
|
867
|
-
// deleteConversation removes superseding items, find superseded items
|
|
868
|
-
// with no supersededBy link where no active item with the same
|
|
869
|
-
// kind + subject + scope_id exists. Scoped to only the kind + subject
|
|
870
|
-
// pairs of items that were just orphaned by deleteConversation, so we
|
|
871
|
-
// don't accidentally restore items superseded by unrelated conversations.
|
|
872
|
-
let orphanedSuperseded: Array<{ id: string }> = [];
|
|
873
|
-
if (orphanedKindSubjects.length > 0) {
|
|
874
|
-
const placeholders = orphanedKindSubjects.map(() => "(?, ?)").join(", ");
|
|
875
|
-
const params: Array<string> = [scopeId];
|
|
876
|
-
for (const { kind, subject } of orphanedKindSubjects) {
|
|
877
|
-
params.push(kind, subject);
|
|
878
|
-
}
|
|
879
|
-
orphanedSuperseded = rawAll<{ id: string }>(
|
|
880
|
-
`SELECT id FROM (
|
|
881
|
-
SELECT id, ROW_NUMBER() OVER (
|
|
882
|
-
PARTITION BY kind, subject, scope_id
|
|
883
|
-
ORDER BY last_seen_at DESC
|
|
884
|
-
) AS rn
|
|
885
|
-
FROM memory_items
|
|
886
|
-
WHERE status = 'superseded'
|
|
887
|
-
AND superseded_by IS NULL
|
|
888
|
-
AND scope_id = ?
|
|
889
|
-
AND (kind, subject) IN (VALUES ${placeholders})
|
|
890
|
-
AND NOT EXISTS (
|
|
891
|
-
SELECT 1 FROM memory_items mi2
|
|
892
|
-
WHERE mi2.kind = memory_items.kind
|
|
893
|
-
AND mi2.subject = memory_items.subject
|
|
894
|
-
AND mi2.scope_id = memory_items.scope_id
|
|
895
|
-
AND mi2.status = 'active'
|
|
896
|
-
AND mi2.id != memory_items.id
|
|
897
|
-
)
|
|
898
|
-
) WHERE rn = 1`,
|
|
899
|
-
...params,
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
for (const { id: itemId } of orphanedSuperseded) {
|
|
903
|
-
rawRun("UPDATE memory_items SET status = 'active' WHERE id = ?", itemId);
|
|
904
|
-
enqueueMemoryJob("embed_item", { itemId });
|
|
905
|
-
unsupersededItemIds.push(itemId);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Step G — Return the combined result.
|
|
779
|
+
// Step E — Return the combined result.
|
|
909
780
|
return {
|
|
910
781
|
...deletedMemoryIds,
|
|
911
|
-
unsupersededItemIds,
|
|
912
782
|
deletedSummaryIds: [
|
|
913
783
|
...deletedSummaryIds,
|
|
914
784
|
...deletedMemoryIds.deletedSummaryIds,
|
|
@@ -938,20 +808,17 @@ export function purgePrivateConversations(): {
|
|
|
938
808
|
count: 0,
|
|
939
809
|
deletedMemory: {
|
|
940
810
|
segmentIds: [],
|
|
941
|
-
orphanedItemIds: [],
|
|
942
811
|
deletedSummaryIds: [],
|
|
943
812
|
},
|
|
944
813
|
};
|
|
945
814
|
}
|
|
946
815
|
|
|
947
816
|
const allSegmentIds: string[] = [];
|
|
948
|
-
const allOrphanedItemIds: string[] = [];
|
|
949
817
|
const allDeletedSummaryIds: string[] = [];
|
|
950
818
|
|
|
951
819
|
for (const conv of privateConvs) {
|
|
952
820
|
const deleted = deleteConversation(conv.id);
|
|
953
821
|
allSegmentIds.push(...deleted.segmentIds);
|
|
954
|
-
allOrphanedItemIds.push(...deleted.orphanedItemIds);
|
|
955
822
|
allDeletedSummaryIds.push(...deleted.deletedSummaryIds);
|
|
956
823
|
}
|
|
957
824
|
|
|
@@ -959,7 +826,6 @@ export function purgePrivateConversations(): {
|
|
|
959
826
|
count: privateConvs.length,
|
|
960
827
|
deletedMemory: {
|
|
961
828
|
segmentIds: allSegmentIds,
|
|
962
|
-
orphanedItemIds: allOrphanedItemIds,
|
|
963
829
|
deletedSummaryIds: allDeletedSummaryIds,
|
|
964
830
|
},
|
|
965
831
|
};
|
|
@@ -1021,7 +887,7 @@ export async function addMessage(
|
|
|
1021
887
|
.run();
|
|
1022
888
|
}
|
|
1023
889
|
tx.update(conversations)
|
|
1024
|
-
.set({ updatedAt: now })
|
|
890
|
+
.set({ updatedAt: now, lastMessageAt: now })
|
|
1025
891
|
.where(eq(conversations.id, conversationId))
|
|
1026
892
|
.run();
|
|
1027
893
|
});
|
|
@@ -1265,18 +1131,16 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
1265
1131
|
const convCount =
|
|
1266
1132
|
rawGet<{ c: number }>("SELECT COUNT(*) AS c FROM conversations")?.c ?? 0;
|
|
1267
1133
|
|
|
1268
|
-
// Delete in dependency order. Cascades handle memory_segments
|
|
1269
|
-
//
|
|
1270
|
-
//
|
|
1134
|
+
// Delete in dependency order. Cascades handle memory_segments and
|
|
1135
|
+
// tool_invocations, but we explicitly clear non-cascading memory
|
|
1136
|
+
// tables too.
|
|
1271
1137
|
//
|
|
1272
1138
|
// FTS virtual tables are cleared before their base tables. If an FTS
|
|
1273
1139
|
// table is corrupted, the DELETE will fail — we drop the associated
|
|
1274
1140
|
// triggers so that the subsequent base-table DELETEs don't also fail
|
|
1275
1141
|
// (SQLite triggers are atomic with the triggering statement, so a
|
|
1276
1142
|
// corrupted FTS table would roll back every base-table DELETE).
|
|
1277
|
-
rawExec("DELETE FROM memory_item_sources");
|
|
1278
1143
|
rawExec("DELETE FROM memory_segments");
|
|
1279
|
-
rawExec("DELETE FROM memory_items");
|
|
1280
1144
|
rawExec("DELETE FROM memory_summaries");
|
|
1281
1145
|
rawExec("DELETE FROM memory_embeddings");
|
|
1282
1146
|
rawExec("DELETE FROM memory_jobs");
|
|
@@ -1398,8 +1262,16 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1398
1262
|
|
|
1399
1263
|
db.transaction((tx) => {
|
|
1400
1264
|
tx.delete(messages).where(condition).run();
|
|
1265
|
+
const maxResult = tx
|
|
1266
|
+
.select({ maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})` })
|
|
1267
|
+
.from(messages)
|
|
1268
|
+
.where(eq(messages.conversationId, conversationId))
|
|
1269
|
+
.get();
|
|
1401
1270
|
tx.update(conversations)
|
|
1402
|
-
.set({
|
|
1271
|
+
.set({
|
|
1272
|
+
updatedAt: Date.now(),
|
|
1273
|
+
lastMessageAt: maxResult?.maxCreatedAt ?? null,
|
|
1274
|
+
})
|
|
1403
1275
|
.where(eq(conversations.id, conversationId))
|
|
1404
1276
|
.run();
|
|
1405
1277
|
});
|
|
@@ -1416,12 +1288,10 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1416
1288
|
*/
|
|
1417
1289
|
export interface DeletedMemoryIds {
|
|
1418
1290
|
segmentIds: string[];
|
|
1419
|
-
orphanedItemIds: string[];
|
|
1420
1291
|
deletedSummaryIds: string[];
|
|
1421
1292
|
}
|
|
1422
1293
|
|
|
1423
1294
|
export interface WipeConversationResult extends DeletedMemoryIds {
|
|
1424
|
-
unsupersededItemIds: string[];
|
|
1425
1295
|
cancelledJobCount: number;
|
|
1426
1296
|
}
|
|
1427
1297
|
|
|
@@ -1440,6 +1310,27 @@ export function updateMessageContent(
|
|
|
1440
1310
|
.run();
|
|
1441
1311
|
}
|
|
1442
1312
|
|
|
1313
|
+
/**
|
|
1314
|
+
* Merge `updates` into the metadata JSON of an existing message.
|
|
1315
|
+
* Reads the current metadata, shallow-merges the new fields, and writes back.
|
|
1316
|
+
*/
|
|
1317
|
+
export function updateMessageMetadata(
|
|
1318
|
+
messageId: string,
|
|
1319
|
+
updates: Record<string, unknown>,
|
|
1320
|
+
): void {
|
|
1321
|
+
const db = getDb();
|
|
1322
|
+
const row = db
|
|
1323
|
+
.select({ metadata: messages.metadata })
|
|
1324
|
+
.from(messages)
|
|
1325
|
+
.where(eq(messages.id, messageId))
|
|
1326
|
+
.get();
|
|
1327
|
+
const existing = row?.metadata ? JSON.parse(row.metadata) : {};
|
|
1328
|
+
db.update(messages)
|
|
1329
|
+
.set({ metadata: JSON.stringify({ ...existing, ...updates }) })
|
|
1330
|
+
.where(eq(messages.id, messageId))
|
|
1331
|
+
.run();
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1443
1334
|
/**
|
|
1444
1335
|
* Re-link all attachments from a set of source messages to a target message.
|
|
1445
1336
|
* Used during message consolidation so that attachments linked to deleted
|
|
@@ -1475,21 +1366,13 @@ export function relinkAttachments(
|
|
|
1475
1366
|
* NULL before the message row is removed, so associated run and event
|
|
1476
1367
|
* records survive.
|
|
1477
1368
|
*
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
* cascades memory_item_sources but leaves the memory_items active.
|
|
1481
|
-
* Without cleanup, those items would leak into summaries and recall.
|
|
1482
|
-
* We delete any memory_items that become orphaned (no remaining sources)
|
|
1483
|
-
* after this message is removed.
|
|
1484
|
-
*
|
|
1485
|
-
* Returns segment and orphaned item IDs so the caller can clean up the
|
|
1486
|
-
* corresponding Qdrant vector entries.
|
|
1369
|
+
* Returns segment IDs so the caller can clean up the corresponding
|
|
1370
|
+
* Qdrant vector entries.
|
|
1487
1371
|
*/
|
|
1488
1372
|
export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
1489
1373
|
const db = getDb();
|
|
1490
1374
|
const result: DeletedMemoryIds = {
|
|
1491
1375
|
segmentIds: [],
|
|
1492
|
-
orphanedItemIds: [],
|
|
1493
1376
|
deletedSummaryIds: [],
|
|
1494
1377
|
};
|
|
1495
1378
|
|
|
@@ -1503,6 +1386,13 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1503
1386
|
.map((r) => r.attachmentId)
|
|
1504
1387
|
.filter((id): id is string => id !== undefined);
|
|
1505
1388
|
|
|
1389
|
+
// Look up the conversation before the transaction so we can recalculate lastMessageAt.
|
|
1390
|
+
const msgRow = db
|
|
1391
|
+
.select({ conversationId: messages.conversationId })
|
|
1392
|
+
.from(messages)
|
|
1393
|
+
.where(eq(messages.id, messageId))
|
|
1394
|
+
.get();
|
|
1395
|
+
|
|
1506
1396
|
db.transaction((tx) => {
|
|
1507
1397
|
// Collect memory segment IDs linked to this message before cascade.
|
|
1508
1398
|
const linkedSegments = tx
|
|
@@ -1512,24 +1402,31 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1512
1402
|
.all();
|
|
1513
1403
|
result.segmentIds = linkedSegments.map((r) => r.id);
|
|
1514
1404
|
|
|
1515
|
-
// Collect memory item IDs linked to this message before cascade.
|
|
1516
|
-
const linkedItems = tx
|
|
1517
|
-
.select({ memoryItemId: memoryItemSources.memoryItemId })
|
|
1518
|
-
.from(memoryItemSources)
|
|
1519
|
-
.where(eq(memoryItemSources.messageId, messageId))
|
|
1520
|
-
.all();
|
|
1521
|
-
const candidateItemIds = linkedItems.map((r) => r.memoryItemId);
|
|
1522
|
-
|
|
1523
1405
|
// Detach nullable FK references so the cascade doesn't destroy them.
|
|
1524
1406
|
tx.update(channelInboundEvents)
|
|
1525
1407
|
.set({ messageId: null })
|
|
1526
1408
|
.where(eq(channelInboundEvents.messageId, messageId))
|
|
1527
1409
|
.run();
|
|
1528
1410
|
|
|
1529
|
-
// Now safe to delete — NOT NULL cascades remove
|
|
1530
|
-
//
|
|
1411
|
+
// Now safe to delete — NOT NULL cascades remove memory_segments
|
|
1412
|
+
// and message_attachments.
|
|
1531
1413
|
tx.delete(messages).where(eq(messages.id, messageId)).run();
|
|
1532
1414
|
|
|
1415
|
+
// Recalculate lastMessageAt after deletion.
|
|
1416
|
+
if (msgRow) {
|
|
1417
|
+
const maxResult = tx
|
|
1418
|
+
.select({
|
|
1419
|
+
maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})`,
|
|
1420
|
+
})
|
|
1421
|
+
.from(messages)
|
|
1422
|
+
.where(eq(messages.conversationId, msgRow.conversationId))
|
|
1423
|
+
.get();
|
|
1424
|
+
tx.update(conversations)
|
|
1425
|
+
.set({ lastMessageAt: maxResult?.maxCreatedAt ?? null })
|
|
1426
|
+
.where(eq(conversations.id, msgRow.conversationId))
|
|
1427
|
+
.run();
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1533
1430
|
// Clean up segment embeddings from SQLite (Qdrant cleanup is the caller's job).
|
|
1534
1431
|
if (result.segmentIds.length > 0) {
|
|
1535
1432
|
tx.delete(memoryEmbeddings)
|
|
@@ -1541,37 +1438,6 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
1541
1438
|
)
|
|
1542
1439
|
.run();
|
|
1543
1440
|
}
|
|
1544
|
-
|
|
1545
|
-
// Clean up orphaned memory items whose only source was this message.
|
|
1546
|
-
if (candidateItemIds.length > 0) {
|
|
1547
|
-
// Find which items still have at least one remaining source.
|
|
1548
|
-
const surviving = tx
|
|
1549
|
-
.select({ memoryItemId: memoryItemSources.memoryItemId })
|
|
1550
|
-
.from(memoryItemSources)
|
|
1551
|
-
.where(inArray(memoryItemSources.memoryItemId, candidateItemIds))
|
|
1552
|
-
.all();
|
|
1553
|
-
const survivingIds = new Set(surviving.map((r) => r.memoryItemId));
|
|
1554
|
-
const orphanedIds = candidateItemIds.filter(
|
|
1555
|
-
(id) => !survivingIds.has(id),
|
|
1556
|
-
);
|
|
1557
|
-
result.orphanedItemIds = orphanedIds;
|
|
1558
|
-
|
|
1559
|
-
if (orphanedIds.length > 0) {
|
|
1560
|
-
// Delete embeddings referencing these items.
|
|
1561
|
-
tx.delete(memoryEmbeddings)
|
|
1562
|
-
.where(
|
|
1563
|
-
and(
|
|
1564
|
-
eq(memoryEmbeddings.targetType, "item"),
|
|
1565
|
-
inArray(memoryEmbeddings.targetId, orphanedIds),
|
|
1566
|
-
),
|
|
1567
|
-
)
|
|
1568
|
-
.run();
|
|
1569
|
-
// Delete the orphaned memory items themselves.
|
|
1570
|
-
tx.delete(memoryItems)
|
|
1571
|
-
.where(inArray(memoryItems.id, orphanedIds))
|
|
1572
|
-
.run();
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
1441
|
});
|
|
1576
1442
|
|
|
1577
1443
|
deleteOrphanAttachments(candidateAttachmentIds);
|
|
@@ -1670,18 +1536,66 @@ export function batchSetDisplayOrders(
|
|
|
1670
1536
|
id: string;
|
|
1671
1537
|
displayOrder: number | null;
|
|
1672
1538
|
isPinned: boolean;
|
|
1539
|
+
groupId?: string | null;
|
|
1673
1540
|
}>,
|
|
1674
1541
|
): void {
|
|
1675
1542
|
ensureDisplayOrderMigration();
|
|
1543
|
+
ensureGroupMigration();
|
|
1676
1544
|
rawExec("BEGIN");
|
|
1677
1545
|
try {
|
|
1678
1546
|
for (const update of updates) {
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1547
|
+
if (update.groupId !== undefined) {
|
|
1548
|
+
// New client: groupId is authoritative.
|
|
1549
|
+
// Derive is_pinned from groupId.
|
|
1550
|
+
// Sanitize: if groupId references a deleted/unknown group, fall back
|
|
1551
|
+
// to NULL to avoid FK violation that would roll back the entire batch.
|
|
1552
|
+
let safeGroupId = update.groupId;
|
|
1553
|
+
if (
|
|
1554
|
+
safeGroupId !== null &&
|
|
1555
|
+
!rawGet<{ id: string }>(
|
|
1556
|
+
"SELECT id FROM conversation_groups WHERE id = ?",
|
|
1557
|
+
safeGroupId,
|
|
1558
|
+
)
|
|
1559
|
+
) {
|
|
1560
|
+
safeGroupId = null;
|
|
1561
|
+
}
|
|
1562
|
+
rawRun(
|
|
1563
|
+
"UPDATE conversations SET display_order = ?, is_pinned = ?, group_id = ? WHERE id = ?",
|
|
1564
|
+
update.displayOrder,
|
|
1565
|
+
safeGroupId === "system:pinned" ? 1 : 0,
|
|
1566
|
+
safeGroupId,
|
|
1567
|
+
update.id,
|
|
1568
|
+
);
|
|
1569
|
+
} else {
|
|
1570
|
+
// Old client: no groupId in payload
|
|
1571
|
+
// isPinned true -> set group_id = system:pinned
|
|
1572
|
+
// isPinned false -> clear group_id ONLY IF currently system:pinned
|
|
1573
|
+
// otherwise preserve existing group_id
|
|
1574
|
+
if (update.isPinned) {
|
|
1575
|
+
rawRun(
|
|
1576
|
+
"UPDATE conversations SET display_order = ?, is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
|
|
1577
|
+
update.displayOrder,
|
|
1578
|
+
update.id,
|
|
1579
|
+
);
|
|
1580
|
+
} else {
|
|
1581
|
+
// Restore system group from source/conversationType when old clients
|
|
1582
|
+
// unpin, instead of clearing to NULL (which would lose provenance).
|
|
1583
|
+
rawRun(
|
|
1584
|
+
`UPDATE conversations SET display_order = ?, is_pinned = 0,
|
|
1585
|
+
group_id = CASE WHEN group_id = 'system:pinned' THEN
|
|
1586
|
+
CASE
|
|
1587
|
+
WHEN source IN ('schedule', 'reminder') THEN 'system:scheduled'
|
|
1588
|
+
WHEN source IN ('heartbeat', 'task') THEN 'system:background'
|
|
1589
|
+
WHEN conversation_type = 'background' AND COALESCE(source, '') != 'notification' THEN 'system:background'
|
|
1590
|
+
ELSE NULL
|
|
1591
|
+
END
|
|
1592
|
+
ELSE group_id END
|
|
1593
|
+
WHERE id = ?`,
|
|
1594
|
+
update.displayOrder,
|
|
1595
|
+
update.id,
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1685
1599
|
}
|
|
1686
1600
|
rawExec("COMMIT");
|
|
1687
1601
|
} catch (err) {
|
|
@@ -1692,21 +1606,30 @@ export function batchSetDisplayOrders(
|
|
|
1692
1606
|
|
|
1693
1607
|
export function getDisplayMetaForConversations(
|
|
1694
1608
|
conversationIds: string[],
|
|
1695
|
-
): Map<
|
|
1609
|
+
): Map<
|
|
1610
|
+
string,
|
|
1611
|
+
{ displayOrder: number | null; isPinned: boolean; groupId: string | null }
|
|
1612
|
+
> {
|
|
1696
1613
|
ensureDisplayOrderMigration();
|
|
1614
|
+
ensureGroupMigration();
|
|
1697
1615
|
const result = new Map<
|
|
1698
1616
|
string,
|
|
1699
|
-
{ displayOrder: number | null; isPinned: boolean }
|
|
1617
|
+
{ displayOrder: number | null; isPinned: boolean; groupId: string | null }
|
|
1700
1618
|
>();
|
|
1701
1619
|
if (conversationIds.length === 0) return result;
|
|
1702
1620
|
for (const id of conversationIds) {
|
|
1703
1621
|
const row = rawGet<{
|
|
1704
1622
|
display_order: number | null;
|
|
1705
1623
|
is_pinned: number | null;
|
|
1706
|
-
|
|
1624
|
+
group_id: string | null;
|
|
1625
|
+
}>(
|
|
1626
|
+
"SELECT display_order, is_pinned, group_id FROM conversations WHERE id = ?",
|
|
1627
|
+
id,
|
|
1628
|
+
);
|
|
1707
1629
|
result.set(id, {
|
|
1708
1630
|
displayOrder: row?.display_order ?? null,
|
|
1709
1631
|
isPinned: (row?.is_pinned ?? 0) === 1,
|
|
1632
|
+
groupId: row?.group_id ?? null,
|
|
1710
1633
|
});
|
|
1711
1634
|
}
|
|
1712
1635
|
return result;
|