@vellumai/assistant 0.4.49 → 0.4.50
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/memory.md +180 -119
- package/package.json +2 -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__/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__/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-vault-unit.test.ts +4 -0
- package/src/__tests__/credential-vault.test.ts +13 -1
- 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__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- 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 +572 -5
- package/src/__tests__/oauth-store.test.ts +120 -6
- 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 +46 -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__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- 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__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- 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 +132 -0
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +43 -5
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +1 -1
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +31 -2
- package/src/cli/commands/oauth/connections.ts +431 -97
- package/src/cli/commands/oauth/providers.ts +15 -1
- 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 +4 -10
- 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-tool-registry.ts +2 -5
- 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/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/sessions.ts +18 -13
- 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 +31 -3
- 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 -0
- package/src/daemon/server.ts +12 -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-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/instrument.ts +61 -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 +22 -3
- package/src/memory/db-init.ts +28 -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/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/index.ts +7 -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/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/providers/gmail/mime-builder.ts +17 -7
- package/src/oauth/byo-connection.test.ts +8 -1
- package/src/oauth/oauth-store.ts +113 -27
- package/src/oauth/seed-providers.ts +6 -0
- package/src/oauth/token-persistence.ts +11 -3
- 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/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/conversation-routes.ts +9 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- 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/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +2 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +36 -36
- 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 +1 -3
- 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/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- 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
|
@@ -6,10 +6,24 @@ import { parseConversation, parseMessage } 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,
|
|
@@ -160,7 +174,9 @@ export function isLastUserMessageToolResult(conversationId: string): boolean {
|
|
|
160
174
|
Array.isArray(parsed) &&
|
|
161
175
|
parsed.length > 0 &&
|
|
162
176
|
parsed.every(
|
|
163
|
-
(block: Record<string, unknown>) =>
|
|
177
|
+
(block: Record<string, unknown>) =>
|
|
178
|
+
block.type === "tool_result" ||
|
|
179
|
+
block.type === "web_search_tool_result",
|
|
164
180
|
)
|
|
165
181
|
) {
|
|
166
182
|
return true;
|
|
@@ -375,7 +391,10 @@ function buildExcerpt(rawContent: string, query: string): string {
|
|
|
375
391
|
if (typeof block === "object" && block != null) {
|
|
376
392
|
if (block.type === "text" && typeof block.text === "string") {
|
|
377
393
|
parts.push(block.text);
|
|
378
|
-
} else if (
|
|
394
|
+
} else if (
|
|
395
|
+
block.type === "tool_result" ||
|
|
396
|
+
block.type === "web_search_tool_result"
|
|
397
|
+
) {
|
|
379
398
|
const inner = Array.isArray(block.content) ? block.content : [];
|
|
380
399
|
for (const ib of inner) {
|
|
381
400
|
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,12 @@ import {
|
|
|
63
67
|
migrateGuardianVerificationPurpose,
|
|
64
68
|
migrateGuardianVerificationSessions,
|
|
65
69
|
migrateInviteCodeHashColumn,
|
|
70
|
+
migrateMemoryItemSupersession,
|
|
66
71
|
migrateMessagesFtsBackfill,
|
|
67
72
|
migrateNormalizePhoneIdentities,
|
|
68
73
|
migrateNotificationDeliveryThreadDecision,
|
|
74
|
+
migrateOAuthAppsClientSecretPath,
|
|
75
|
+
migrateOAuthProvidersPingUrl,
|
|
69
76
|
migrateReminderRoutingIntent,
|
|
70
77
|
migrateRemindersToSchedules,
|
|
71
78
|
migrateRenameGuardianVerificationValues,
|
|
@@ -344,6 +351,27 @@ export function initializeDb(): void {
|
|
|
344
351
|
// 53. OAuth provider/app/connection tables
|
|
345
352
|
createOAuthTables(database);
|
|
346
353
|
|
|
354
|
+
// 54. Add explicit client_secret_credential_path to oauth_apps
|
|
355
|
+
migrateOAuthAppsClientSecretPath(database);
|
|
356
|
+
|
|
357
|
+
// 55. Add ping_url column to oauth_providers
|
|
358
|
+
migrateOAuthProvidersPingUrl(database);
|
|
359
|
+
|
|
360
|
+
// 56. Add supersession tracking columns and override confidence to memory_items
|
|
361
|
+
migrateMemoryItemSupersession(database);
|
|
362
|
+
|
|
363
|
+
// 56b. Drop unused entity tables (entity search replaced by hybrid search on item statements)
|
|
364
|
+
migrateDropEntityTables(database);
|
|
365
|
+
|
|
366
|
+
// 57. Drop memory_segment_fts virtual table and triggers (replaced by Qdrant hybrid search)
|
|
367
|
+
migrateDropMemorySegmentFts(database);
|
|
368
|
+
|
|
369
|
+
// 58. Drop memory_item_conflicts table (conflict resolution system removed)
|
|
370
|
+
migrateDropConflicts(database);
|
|
371
|
+
|
|
372
|
+
// 59. Add invite metadata columns to call_sessions for outbound invite call routing
|
|
373
|
+
migrateCallSessionInviteMetadata(database);
|
|
374
|
+
|
|
347
375
|
validateMigrationState(database);
|
|
348
376
|
|
|
349
377
|
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
|
}
|