@vellumai/assistant 0.4.49 → 0.4.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +24 -33
- package/README.md +3 -3
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/docs/architecture/memory.md +180 -119
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +23 -18
- package/src/__tests__/credential-vault.test.ts +30 -18
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +166 -13
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +824 -31
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +363 -17
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +55 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +78 -18
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +85 -5
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +134 -3
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +44 -6
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +5 -4
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +11 -8
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +68 -10
- package/src/cli/commands/oauth/connections.ts +475 -105
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +18 -4
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +20 -22
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/loader.ts +6 -42
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +55 -2
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +39 -4
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -42
- package/src/daemon/server.ts +6 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +3 -5
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/email/providers/index.ts +2 -2
- package/src/instrument.ts +61 -1
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +25 -83
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +40 -25
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +183 -31
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +214 -8
- package/src/oauth/token-persistence.ts +31 -16
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +72 -12
- package/src/runtime/invite-service.ts +43 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +56 -11
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +9 -4
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +44 -19
- package/src/security/token-manager.ts +46 -39
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +22 -7
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +10 -3
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
conversations,
|
|
20
20
|
llmRequestLogs,
|
|
21
21
|
memoryEmbeddings,
|
|
22
|
-
memoryItemEntities,
|
|
23
22
|
memoryItems,
|
|
24
23
|
memoryItemSources,
|
|
25
24
|
memorySegments,
|
|
@@ -503,19 +502,6 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
503
502
|
// triggers so that the subsequent base-table DELETEs don't also fail
|
|
504
503
|
// (SQLite triggers are atomic with the triggering statement, so a
|
|
505
504
|
// corrupted FTS table would roll back every base-table DELETE).
|
|
506
|
-
let segmentFtsCorrupted = false;
|
|
507
|
-
try {
|
|
508
|
-
rawExec("DELETE FROM memory_segment_fts");
|
|
509
|
-
} catch (err) {
|
|
510
|
-
log.warn(
|
|
511
|
-
{ err },
|
|
512
|
-
"clearAll: failed to clear memory_segment_fts — dropping triggers so base-table cleanup can proceed",
|
|
513
|
-
);
|
|
514
|
-
rawExec("DROP TRIGGER IF EXISTS memory_segments_ai");
|
|
515
|
-
rawExec("DROP TRIGGER IF EXISTS memory_segments_ad");
|
|
516
|
-
rawExec("DROP TRIGGER IF EXISTS memory_segments_au");
|
|
517
|
-
segmentFtsCorrupted = true;
|
|
518
|
-
}
|
|
519
505
|
rawExec("DELETE FROM memory_item_sources");
|
|
520
506
|
rawExec("DELETE FROM memory_segments");
|
|
521
507
|
rawExec("DELETE FROM memory_items");
|
|
@@ -548,21 +534,6 @@ export function clearAll(): { conversations: number; messages: number } {
|
|
|
548
534
|
// DELETEs have completed. Dropping the virtual table clears the corruption,
|
|
549
535
|
// and recreating it + triggers means subsequent writes maintain FTS
|
|
550
536
|
// consistency without requiring a daemon restart.
|
|
551
|
-
if (segmentFtsCorrupted) {
|
|
552
|
-
rawExec("DROP TABLE IF EXISTS memory_segment_fts");
|
|
553
|
-
rawExec(
|
|
554
|
-
`CREATE VIRTUAL TABLE IF NOT EXISTS memory_segment_fts USING fts5(segment_id UNINDEXED, text)`,
|
|
555
|
-
);
|
|
556
|
-
rawExec(
|
|
557
|
-
`CREATE TRIGGER IF NOT EXISTS memory_segments_ai AFTER INSERT ON memory_segments BEGIN INSERT INTO memory_segment_fts(segment_id, text) VALUES (new.id, new.text); END`,
|
|
558
|
-
);
|
|
559
|
-
rawExec(
|
|
560
|
-
`CREATE TRIGGER IF NOT EXISTS memory_segments_ad AFTER DELETE ON memory_segments BEGIN DELETE FROM memory_segment_fts WHERE segment_id = old.id; END`,
|
|
561
|
-
);
|
|
562
|
-
rawExec(
|
|
563
|
-
`CREATE TRIGGER IF NOT EXISTS memory_segments_au AFTER UPDATE ON memory_segments BEGIN DELETE FROM memory_segment_fts WHERE segment_id = old.id; INSERT INTO memory_segment_fts(segment_id, text) VALUES (new.id, new.text); END`,
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
537
|
if (messagesFtsCorrupted) {
|
|
567
538
|
rawExec("DROP TABLE IF EXISTS messages_fts");
|
|
568
539
|
rawExec(
|
|
@@ -787,10 +758,6 @@ export function deleteMessageById(messageId: string): DeletedMemoryIds {
|
|
|
787
758
|
result.orphanedItemIds = orphanedIds;
|
|
788
759
|
|
|
789
760
|
if (orphanedIds.length > 0) {
|
|
790
|
-
// Delete memory_item_entities (no FK cascade on this table).
|
|
791
|
-
tx.delete(memoryItemEntities)
|
|
792
|
-
.where(inArray(memoryItemEntities.memoryItemId, orphanedIds))
|
|
793
|
-
.run();
|
|
794
761
|
// Delete embeddings referencing these items.
|
|
795
762
|
tx.delete(memoryEmbeddings)
|
|
796
763
|
.where(
|
|
@@ -1,15 +1,29 @@
|
|
|
1
|
-
import { and,
|
|
1
|
+
import { and, count, desc, eq, sql } from "drizzle-orm";
|
|
2
2
|
|
|
3
3
|
import { getLogger } from "../util/logger.js";
|
|
4
|
-
import type { ConversationRow
|
|
5
|
-
import { parseConversation
|
|
4
|
+
import type { ConversationRow } from "./conversation-crud.js";
|
|
5
|
+
import { parseConversation } from "./conversation-crud.js";
|
|
6
6
|
import { ensureDisplayOrderMigration } from "./conversation-display-order-migration.js";
|
|
7
7
|
import { getDb, rawAll } from "./db.js";
|
|
8
8
|
import { conversations, messages } from "./schema.js";
|
|
9
|
-
import { buildFtsMatchQuery } from "./search/lexical.js";
|
|
10
9
|
|
|
11
10
|
const log = getLogger("conversation-store");
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Build an FTS5 MATCH query string from natural text by extracting tokens.
|
|
14
|
+
* Used for messages_fts full-text search over conversation content.
|
|
15
|
+
*/
|
|
16
|
+
function buildFtsMatchQuery(text: string): string | null {
|
|
17
|
+
const tokens = text
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.split(/[^a-z0-9_]+/g)
|
|
20
|
+
.map((token) => token.trim())
|
|
21
|
+
.filter((token) => token.length >= 2);
|
|
22
|
+
if (tokens.length === 0) return null;
|
|
23
|
+
const unique = [...new Set(tokens)].slice(0, 24);
|
|
24
|
+
return unique.map((token) => `"${token.replace(/"/g, '""')}"`).join(" OR ");
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
export function listConversations(
|
|
14
28
|
limit?: number,
|
|
15
29
|
includeBackground = false,
|
|
@@ -55,83 +69,6 @@ export function getLatestConversation(): ConversationRow | null {
|
|
|
55
69
|
return row ? parseConversation(row) : null;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
export interface PaginatedMessagesResult {
|
|
59
|
-
messages: MessageRow[];
|
|
60
|
-
/** Whether older messages exist beyond the returned page. */
|
|
61
|
-
hasMore: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Paginated variant of getMessages. Returns the most recent `limit` messages
|
|
66
|
-
* (optionally before a cursor timestamp), in chronological order.
|
|
67
|
-
*
|
|
68
|
-
* When `limit` is undefined, all matching messages are returned (no pagination).
|
|
69
|
-
* When `beforeMessageId` is provided alongside `beforeTimestamp`, it acts as a
|
|
70
|
-
* tie-breaker to avoid skipping messages that share the same millisecond timestamp
|
|
71
|
-
* at page boundaries.
|
|
72
|
-
*/
|
|
73
|
-
export function getMessagesPaginated(
|
|
74
|
-
conversationId: string,
|
|
75
|
-
limit: number | undefined,
|
|
76
|
-
beforeTimestamp?: number,
|
|
77
|
-
beforeMessageId?: string,
|
|
78
|
-
): PaginatedMessagesResult {
|
|
79
|
-
const db = getDb();
|
|
80
|
-
const conditions = [eq(messages.conversationId, conversationId)];
|
|
81
|
-
if (beforeTimestamp !== undefined) {
|
|
82
|
-
if (beforeMessageId) {
|
|
83
|
-
// Proper compound cursor: fetch messages that are strictly older, OR
|
|
84
|
-
// share the same timestamp but have a smaller ID. This avoids both
|
|
85
|
-
// duplicates and skipped messages when multiple rows share a timestamp.
|
|
86
|
-
conditions.push(
|
|
87
|
-
or(
|
|
88
|
-
lt(messages.createdAt, beforeTimestamp),
|
|
89
|
-
and(
|
|
90
|
-
eq(messages.createdAt, beforeTimestamp),
|
|
91
|
-
lt(messages.id, beforeMessageId),
|
|
92
|
-
),
|
|
93
|
-
)!,
|
|
94
|
-
);
|
|
95
|
-
} else {
|
|
96
|
-
// Legacy callers without a message ID tie-breaker: use strict lt.
|
|
97
|
-
// This may skip same-millisecond messages at boundaries, but avoids
|
|
98
|
-
// re-fetching the boundary message. New callers should prefer the
|
|
99
|
-
// compound cursor (beforeTimestamp + beforeMessageId).
|
|
100
|
-
conditions.push(lt(messages.createdAt, beforeTimestamp));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (limit === undefined) {
|
|
105
|
-
// Unlimited: return all messages in chronological order, no pagination.
|
|
106
|
-
const rows = db
|
|
107
|
-
.select()
|
|
108
|
-
.from(messages)
|
|
109
|
-
.where(and(...conditions))
|
|
110
|
-
.orderBy(asc(messages.createdAt), asc(messages.id))
|
|
111
|
-
.all()
|
|
112
|
-
.map(parseMessage);
|
|
113
|
-
return { messages: rows, hasMore: false };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Fetch limit+1 rows ordered newest-first so we can detect hasMore
|
|
117
|
-
const rows = db
|
|
118
|
-
.select()
|
|
119
|
-
.from(messages)
|
|
120
|
-
.where(and(...conditions))
|
|
121
|
-
.orderBy(desc(messages.createdAt), desc(messages.id))
|
|
122
|
-
.limit(limit + 1)
|
|
123
|
-
.all()
|
|
124
|
-
.map(parseMessage);
|
|
125
|
-
|
|
126
|
-
const hasMore = rows.length > limit;
|
|
127
|
-
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
128
|
-
|
|
129
|
-
// Return in chronological order (oldest first) for the client
|
|
130
|
-
page.reverse();
|
|
131
|
-
|
|
132
|
-
return { messages: page, hasMore };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
72
|
/**
|
|
136
73
|
* Check whether the last user message in a conversation is a tool_result-only
|
|
137
74
|
* message (i.e., not a real user-typed message). This is used by undo() to
|
|
@@ -160,7 +97,9 @@ export function isLastUserMessageToolResult(conversationId: string): boolean {
|
|
|
160
97
|
Array.isArray(parsed) &&
|
|
161
98
|
parsed.length > 0 &&
|
|
162
99
|
parsed.every(
|
|
163
|
-
(block: Record<string, unknown>) =>
|
|
100
|
+
(block: Record<string, unknown>) =>
|
|
101
|
+
block.type === "tool_result" ||
|
|
102
|
+
block.type === "web_search_tool_result",
|
|
164
103
|
)
|
|
165
104
|
) {
|
|
166
105
|
return true;
|
|
@@ -375,7 +314,10 @@ function buildExcerpt(rawContent: string, query: string): string {
|
|
|
375
314
|
if (typeof block === "object" && block != null) {
|
|
376
315
|
if (block.type === "text" && typeof block.text === "string") {
|
|
377
316
|
parts.push(block.text);
|
|
378
|
-
} else if (
|
|
317
|
+
} else if (
|
|
318
|
+
block.type === "tool_result" ||
|
|
319
|
+
block.type === "web_search_tool_result"
|
|
320
|
+
) {
|
|
379
321
|
const inner = Array.isArray(block.content) ? block.content : [];
|
|
380
322
|
for (const ib of inner) {
|
|
381
323
|
if (ib?.type === "text" && typeof ib.text === "string")
|
package/src/memory/db-init.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
migrateBackfillContactInteractionStats,
|
|
38
38
|
migrateBackfillGuardianPrincipalId,
|
|
39
39
|
migrateBackfillUsageCacheAccounting,
|
|
40
|
+
migrateCallSessionInviteMetadata,
|
|
40
41
|
migrateCallSessionMode,
|
|
41
42
|
migrateCanonicalGuardianDeliveriesDestinationIndex,
|
|
42
43
|
migrateCanonicalGuardianRequesterChatId,
|
|
@@ -49,7 +50,10 @@ import {
|
|
|
49
50
|
migrateConversationsThreadTypeIndex,
|
|
50
51
|
migrateDropAccountsTable,
|
|
51
52
|
migrateDropAssistantIdColumns,
|
|
53
|
+
migrateDropConflicts,
|
|
54
|
+
migrateDropEntityTables,
|
|
52
55
|
migrateDropLegacyMemberGuardianTables,
|
|
56
|
+
migrateDropMemorySegmentFts,
|
|
53
57
|
migrateDropRemindersTable,
|
|
54
58
|
migrateDropUsageCompositeIndexes,
|
|
55
59
|
migrateFkCascadeRebuilds,
|
|
@@ -63,9 +67,13 @@ import {
|
|
|
63
67
|
migrateGuardianVerificationPurpose,
|
|
64
68
|
migrateGuardianVerificationSessions,
|
|
65
69
|
migrateInviteCodeHashColumn,
|
|
70
|
+
migrateInviteContactId,
|
|
71
|
+
migrateMemoryItemSupersession,
|
|
66
72
|
migrateMessagesFtsBackfill,
|
|
67
73
|
migrateNormalizePhoneIdentities,
|
|
68
74
|
migrateNotificationDeliveryThreadDecision,
|
|
75
|
+
migrateOAuthAppsClientSecretPath,
|
|
76
|
+
migrateOAuthProvidersPingUrl,
|
|
69
77
|
migrateReminderRoutingIntent,
|
|
70
78
|
migrateRemindersToSchedules,
|
|
71
79
|
migrateRenameGuardianVerificationValues,
|
|
@@ -344,6 +352,30 @@ export function initializeDb(): void {
|
|
|
344
352
|
// 53. OAuth provider/app/connection tables
|
|
345
353
|
createOAuthTables(database);
|
|
346
354
|
|
|
355
|
+
// 54. Add explicit client_secret_credential_path to oauth_apps
|
|
356
|
+
migrateOAuthAppsClientSecretPath(database);
|
|
357
|
+
|
|
358
|
+
// 55. Add ping_url column to oauth_providers
|
|
359
|
+
migrateOAuthProvidersPingUrl(database);
|
|
360
|
+
|
|
361
|
+
// 56. Add supersession tracking columns and override confidence to memory_items
|
|
362
|
+
migrateMemoryItemSupersession(database);
|
|
363
|
+
|
|
364
|
+
// 56b. Drop unused entity tables (entity search replaced by hybrid search on item statements)
|
|
365
|
+
migrateDropEntityTables(database);
|
|
366
|
+
|
|
367
|
+
// 57. Drop memory_segment_fts virtual table and triggers (replaced by Qdrant hybrid search)
|
|
368
|
+
migrateDropMemorySegmentFts(database);
|
|
369
|
+
|
|
370
|
+
// 58. Drop memory_item_conflicts table (conflict resolution system removed)
|
|
371
|
+
migrateDropConflicts(database);
|
|
372
|
+
|
|
373
|
+
// 59. Add invite metadata columns to call_sessions for outbound invite call routing
|
|
374
|
+
migrateCallSessionInviteMetadata(database);
|
|
375
|
+
|
|
376
|
+
// 60. Add required contact_id to assistant_ingress_invites and clean up legacy rows
|
|
377
|
+
migrateInviteContactId(database);
|
|
378
|
+
|
|
347
379
|
validateMigrationState(database);
|
|
348
380
|
|
|
349
381
|
if (process.env.BUN_TEST === "1") {
|
|
@@ -11,14 +11,11 @@ import {
|
|
|
11
11
|
embeddingInputContentHash,
|
|
12
12
|
type MultimodalEmbeddingInput,
|
|
13
13
|
normalizeEmbeddingInput,
|
|
14
|
+
type SparseEmbedding,
|
|
14
15
|
type TextEmbeddingInput,
|
|
15
16
|
} from "./embedding-types.js";
|
|
16
17
|
|
|
17
|
-
export type {
|
|
18
|
-
EmbeddingInput,
|
|
19
|
-
MultimodalEmbeddingInput,
|
|
20
|
-
TextEmbeddingInput,
|
|
21
|
-
};
|
|
18
|
+
export type { EmbeddingInput, MultimodalEmbeddingInput, TextEmbeddingInput };
|
|
22
19
|
export { embeddingInputContentHash, normalizeEmbeddingInput };
|
|
23
20
|
|
|
24
21
|
const log = getLogger("memory-embeddings");
|
|
@@ -412,7 +409,12 @@ export async function embedWithBackend(
|
|
|
412
409
|
|
|
413
410
|
// ── In-memory cache check (primary provider only) ──────────────
|
|
414
411
|
const cached: (number[] | null)[] = inputs.map((input) => {
|
|
415
|
-
const v = getFromVectorCache(
|
|
412
|
+
const v = getFromVectorCache(
|
|
413
|
+
primaryProvider,
|
|
414
|
+
primaryModel,
|
|
415
|
+
input,
|
|
416
|
+
vectorExtras,
|
|
417
|
+
);
|
|
416
418
|
if (v && v.length === expectedDim) return v;
|
|
417
419
|
return null;
|
|
418
420
|
});
|
|
@@ -443,7 +445,8 @@ export async function embedWithBackend(
|
|
|
443
445
|
|
|
444
446
|
// Skip text-only backends for multimodal inputs
|
|
445
447
|
const hasNonText = inputsToEmbed.some(
|
|
446
|
-
(i) =>
|
|
448
|
+
(i) =>
|
|
449
|
+
typeof i !== "string" && normalizeEmbeddingInput(i).type !== "text",
|
|
447
450
|
);
|
|
448
451
|
if (backend.provider !== "gemini" && hasNonText) {
|
|
449
452
|
continue;
|
|
@@ -502,7 +505,8 @@ export async function embedWithBackend(
|
|
|
502
505
|
}
|
|
503
506
|
if (!anyBackendAttempted) {
|
|
504
507
|
const hasMultimodal = inputs.some(
|
|
505
|
-
(i) =>
|
|
508
|
+
(i) =>
|
|
509
|
+
typeof i !== "string" && normalizeEmbeddingInput(i).type !== "text",
|
|
506
510
|
);
|
|
507
511
|
if (hasMultimodal) {
|
|
508
512
|
throw new Error(
|
|
@@ -614,3 +618,75 @@ function isOllamaConfigured(config: AssistantConfig): boolean {
|
|
|
614
618
|
Boolean(getOllamaBaseUrlEnv())
|
|
615
619
|
);
|
|
616
620
|
}
|
|
621
|
+
|
|
622
|
+
// ── TF-IDF sparse embedding ───────────────────────────────────────
|
|
623
|
+
// Simple tokenizer + TF-IDF sparse encoder. Produces a SparseEmbedding
|
|
624
|
+
// with term indices (hashed to a fixed vocabulary) and TF-IDF weights.
|
|
625
|
+
// Can be upgraded to a learned sparse encoder (e.g. SPLADE) later.
|
|
626
|
+
|
|
627
|
+
const SPARSE_VOCAB_SIZE = 30_000;
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Bump this version whenever the sparse embedding algorithm changes
|
|
631
|
+
* (e.g. hash function fix, tokenizer change) to trigger re-indexing
|
|
632
|
+
* of existing sparse vectors via the sentinel mismatch mechanism.
|
|
633
|
+
*/
|
|
634
|
+
export const SPARSE_EMBEDDING_VERSION = 2;
|
|
635
|
+
|
|
636
|
+
/** Tokenize text into lowercase alphanumeric tokens (Unicode-aware). */
|
|
637
|
+
function tokenize(text: string): string[] {
|
|
638
|
+
return text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Hash a token to a stable index in [0, vocabSize). */
|
|
642
|
+
function tokenHash(token: string, vocabSize: number): number {
|
|
643
|
+
// FNV-1a 32-bit hash for speed
|
|
644
|
+
let hash = 0x811c9dc5;
|
|
645
|
+
for (let i = 0; i < token.length; i++) {
|
|
646
|
+
hash ^= token.charCodeAt(i);
|
|
647
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
648
|
+
}
|
|
649
|
+
return hash % vocabSize;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Generate a TF-IDF-based sparse embedding for the given text.
|
|
654
|
+
*
|
|
655
|
+
* Term frequency is computed from the input. IDF is approximated using
|
|
656
|
+
* sub-linear TF weighting (1 + log(tf)) since we don't have a corpus-level
|
|
657
|
+
* document frequency table. This still produces useful sparse vectors for
|
|
658
|
+
* lexical matching via Qdrant's sparse vector support.
|
|
659
|
+
*/
|
|
660
|
+
export function generateSparseEmbedding(text: string): SparseEmbedding {
|
|
661
|
+
const tokens = tokenize(text);
|
|
662
|
+
if (tokens.length === 0) {
|
|
663
|
+
return { indices: [], values: [] };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Count term frequencies per hash bucket
|
|
667
|
+
const tf = new Map<number, number>();
|
|
668
|
+
for (const token of tokens) {
|
|
669
|
+
const idx = tokenHash(token, SPARSE_VOCAB_SIZE);
|
|
670
|
+
tf.set(idx, (tf.get(idx) ?? 0) + 1);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Convert to sub-linear TF weights: 1 + log(tf)
|
|
674
|
+
const indices: number[] = [];
|
|
675
|
+
const values: number[] = [];
|
|
676
|
+
for (const [idx, count] of tf) {
|
|
677
|
+
indices.push(idx);
|
|
678
|
+
values.push(1 + Math.log(count));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// L2-normalize the sparse vector so scores are comparable
|
|
682
|
+
let norm = 0;
|
|
683
|
+
for (const v of values) norm += v * v;
|
|
684
|
+
norm = Math.sqrt(norm);
|
|
685
|
+
if (norm > 0) {
|
|
686
|
+
for (let i = 0; i < values.length; i++) {
|
|
687
|
+
values[i] /= norm;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return { indices, values };
|
|
692
|
+
}
|
|
@@ -42,11 +42,19 @@ export type MultimodalEmbeddingInput =
|
|
|
42
42
|
/** Accepts raw strings as shorthand for text inputs. */
|
|
43
43
|
export type EmbeddingInput = string | MultimodalEmbeddingInput;
|
|
44
44
|
|
|
45
|
-
export function normalizeEmbeddingInput(
|
|
45
|
+
export function normalizeEmbeddingInput(
|
|
46
|
+
input: EmbeddingInput,
|
|
47
|
+
): MultimodalEmbeddingInput {
|
|
46
48
|
if (typeof input === "string") return { type: "text", text: input };
|
|
47
49
|
return input;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
/** Sparse vector representation: parallel arrays of term indices and weights. */
|
|
53
|
+
export interface SparseEmbedding {
|
|
54
|
+
indices: number[];
|
|
55
|
+
values: number[];
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
export function embeddingInputContentHash(input: EmbeddingInput): string {
|
|
51
59
|
const normalized = normalizeEmbeddingInput(input);
|
|
52
60
|
const hash = createHash("sha256");
|
package/src/memory/indexer.ts
CHANGED
|
@@ -5,24 +5,17 @@ import { getConfig } from "../config/loader.js";
|
|
|
5
5
|
import type { MemoryConfig } from "../config/types.js";
|
|
6
6
|
import type { TrustClass } from "../runtime/actor-trust-resolver.js";
|
|
7
7
|
import { getLogger } from "../util/logger.js";
|
|
8
|
-
import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
|
|
9
8
|
import { getDb } from "./db.js";
|
|
10
9
|
import { selectedBackendSupportsMultimodal } from "./embedding-backend.js";
|
|
11
|
-
import {
|
|
12
|
-
enqueueMemoryJob,
|
|
13
|
-
enqueueResolvePendingConflictsForMessageJob,
|
|
14
|
-
} from "./jobs-store.js";
|
|
10
|
+
import { enqueueMemoryJob } from "./jobs-store.js";
|
|
15
11
|
import {
|
|
16
12
|
extractMediaBlocks,
|
|
17
13
|
extractTextFromStoredMessageContent,
|
|
18
14
|
} from "./message-content.js";
|
|
19
|
-
import { bumpMemoryVersion } from "./recall-cache.js";
|
|
20
15
|
import { memorySegments } from "./schema.js";
|
|
21
16
|
import { segmentText } from "./segmenter.js";
|
|
22
17
|
|
|
23
18
|
const log = getLogger("memory-indexer");
|
|
24
|
-
const SUMMARY_JOB_CHECKPOINT_KEY = "memory:summary_jobs:last_scheduled_at";
|
|
25
|
-
const SUMMARY_SCHEDULE_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
26
19
|
|
|
27
20
|
export interface IndexMessageInput {
|
|
28
21
|
messageId: string;
|
|
@@ -34,8 +27,8 @@ export interface IndexMessageInput {
|
|
|
34
27
|
/**
|
|
35
28
|
* Trust class of the actor who produced this message, captured at
|
|
36
29
|
* persist time. When `'guardian'` or `undefined` (legacy), extraction
|
|
37
|
-
*
|
|
38
|
-
*
|
|
30
|
+
* jobs run. Otherwise, the message is segmented and embedded but no
|
|
31
|
+
* profile mutations are triggered.
|
|
39
32
|
*/
|
|
40
33
|
provenanceTrustClass?: TrustClass;
|
|
41
34
|
}
|
|
@@ -52,7 +45,7 @@ export function indexMessageNow(
|
|
|
52
45
|
if (!config.enabled) return { indexedSegments: 0, enqueuedJobs: 0 };
|
|
53
46
|
|
|
54
47
|
// Provenance-based trust gating: only guardian and legacy (undefined) actors
|
|
55
|
-
// are trusted for extraction
|
|
48
|
+
// are trusted for extraction.
|
|
56
49
|
const isTrustedActor =
|
|
57
50
|
input.provenanceTrustClass === "guardian" ||
|
|
58
51
|
input.provenanceTrustClass === undefined;
|
|
@@ -75,9 +68,6 @@ export function indexMessageNow(
|
|
|
75
68
|
const shouldExtract =
|
|
76
69
|
input.role === "user" ||
|
|
77
70
|
(input.role === "assistant" && config.extraction.extractFromAssistant);
|
|
78
|
-
const shouldResolveConflicts =
|
|
79
|
-
input.role === "user" && config.conflicts.enabled;
|
|
80
|
-
|
|
81
71
|
// Check if the resolved embedding backend supports multimodal input.
|
|
82
72
|
// Only enqueue embed_attachment jobs when it does (currently Gemini only).
|
|
83
73
|
const supportsMultimodal = selectedBackendSupportsMultimodal(getConfig());
|
|
@@ -152,13 +142,6 @@ export function indexMessageNow(
|
|
|
152
142
|
tx,
|
|
153
143
|
);
|
|
154
144
|
}
|
|
155
|
-
if (shouldResolveConflicts && isTrustedActor) {
|
|
156
|
-
enqueueResolvePendingConflictsForMessageJob(
|
|
157
|
-
input.messageId,
|
|
158
|
-
input.scopeId ?? "default",
|
|
159
|
-
tx,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
145
|
enqueueMemoryJob(
|
|
163
146
|
"build_conversation_summary",
|
|
164
147
|
{ conversationId: input.conversationId },
|
|
@@ -173,27 +156,18 @@ export function indexMessageNow(
|
|
|
173
156
|
);
|
|
174
157
|
}
|
|
175
158
|
|
|
176
|
-
|
|
177
|
-
// so lexical/recency retrieval doesn't serve stale results during worker lag.
|
|
178
|
-
if (segments.length - skippedEmbedJobs > 0) {
|
|
179
|
-
bumpMemoryVersion();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!isTrustedActor && (shouldExtract || shouldResolveConflicts)) {
|
|
159
|
+
if (!isTrustedActor && shouldExtract) {
|
|
183
160
|
log.info(
|
|
184
|
-
`Skipping extraction
|
|
161
|
+
`Skipping extraction jobs for untrusted actor (trustClass=${input.provenanceTrustClass})`,
|
|
185
162
|
);
|
|
186
163
|
}
|
|
187
164
|
|
|
188
|
-
enqueueSummaryRollupJobsIfDue();
|
|
189
|
-
|
|
190
165
|
const extractionGated = !isTrustedActor;
|
|
191
166
|
const enqueuedJobs =
|
|
192
167
|
segments.length -
|
|
193
168
|
skippedEmbedJobs +
|
|
194
169
|
mediaBlocks.length +
|
|
195
|
-
(shouldExtract && !extractionGated ? 2 : 1)
|
|
196
|
-
(shouldResolveConflicts && !extractionGated ? 1 : 0);
|
|
170
|
+
(shouldExtract && !extractionGated ? 2 : 1);
|
|
197
171
|
return {
|
|
198
172
|
indexedSegments: segments.length,
|
|
199
173
|
enqueuedJobs,
|
|
@@ -222,19 +196,6 @@ export function getRecentSegmentsForConversation(
|
|
|
222
196
|
.all();
|
|
223
197
|
}
|
|
224
198
|
|
|
225
|
-
function enqueueSummaryRollupJobsIfDue(): void {
|
|
226
|
-
const now = Date.now();
|
|
227
|
-
const raw = getMemoryCheckpoint(SUMMARY_JOB_CHECKPOINT_KEY);
|
|
228
|
-
const last = raw ? Number.parseInt(raw, 10) : 0;
|
|
229
|
-
if (Number.isFinite(last) && now - last < SUMMARY_SCHEDULE_INTERVAL_MS)
|
|
230
|
-
return;
|
|
231
|
-
|
|
232
|
-
enqueueMemoryJob("refresh_weekly_summary", {});
|
|
233
|
-
enqueueMemoryJob("refresh_monthly_summary", {});
|
|
234
|
-
setMemoryCheckpoint(SUMMARY_JOB_CHECKPOINT_KEY, String(now));
|
|
235
|
-
log.debug("Scheduled periodic global summary jobs");
|
|
236
|
-
}
|
|
237
|
-
|
|
238
199
|
function buildSegmentId(messageId: string, segmentIndex: number): string {
|
|
239
200
|
return `${messageId}:${segmentIndex}`;
|
|
240
201
|
}
|
|
@@ -41,6 +41,8 @@ export interface IngressInvite {
|
|
|
41
41
|
// Display metadata for personalized voice prompts (null for non-voice invites)
|
|
42
42
|
friendName: string | null;
|
|
43
43
|
guardianName: string | null;
|
|
44
|
+
// Contact binding — every invite is bound to a specific contact
|
|
45
|
+
contactId: string;
|
|
44
46
|
createdAt: number;
|
|
45
47
|
updatedAt: number;
|
|
46
48
|
}
|
|
@@ -86,6 +88,7 @@ function rowToInvite(
|
|
|
86
88
|
inviteCodeHash: row.inviteCodeHash,
|
|
87
89
|
friendName: row.friendName,
|
|
88
90
|
guardianName: row.guardianName,
|
|
91
|
+
contactId: row.contactId,
|
|
89
92
|
createdAt: row.createdAt,
|
|
90
93
|
updatedAt: row.updatedAt,
|
|
91
94
|
};
|
|
@@ -97,6 +100,7 @@ function rowToInvite(
|
|
|
97
100
|
|
|
98
101
|
export function createInvite(params: {
|
|
99
102
|
sourceChannel: string;
|
|
103
|
+
contactId: string;
|
|
100
104
|
createdBySessionId?: string;
|
|
101
105
|
note?: string;
|
|
102
106
|
maxUses?: number;
|
|
@@ -135,6 +139,7 @@ export function createInvite(params: {
|
|
|
135
139
|
inviteCodeHash: params.inviteCodeHash ?? null,
|
|
136
140
|
friendName: params.friendName ?? null,
|
|
137
141
|
guardianName: params.guardianName ?? null,
|
|
142
|
+
contactId: params.contactId,
|
|
138
143
|
createdAt: now,
|
|
139
144
|
updatedAt: now,
|
|
140
145
|
};
|
|
@@ -312,6 +317,20 @@ export function findByTokenHash(tokenHash: string): IngressInvite | null {
|
|
|
312
317
|
return row ? rowToInvite(row) : null;
|
|
313
318
|
}
|
|
314
319
|
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// findById
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
export function findById(inviteId: string): IngressInvite | null {
|
|
325
|
+
const db = getDb();
|
|
326
|
+
const row = db
|
|
327
|
+
.select()
|
|
328
|
+
.from(assistantIngressInvites)
|
|
329
|
+
.where(eq(assistantIngressInvites.id, inviteId))
|
|
330
|
+
.get();
|
|
331
|
+
return row ? rowToInvite(row) : null;
|
|
332
|
+
}
|
|
333
|
+
|
|
315
334
|
// ---------------------------------------------------------------------------
|
|
316
335
|
// findActiveVoiceInvites
|
|
317
336
|
// ---------------------------------------------------------------------------
|