@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
|
@@ -1,143 +1,10 @@
|
|
|
1
1
|
import { and, desc, eq, inArray, notInArray } from "drizzle-orm";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { getDb, rawAll } from "../db.js";
|
|
3
|
+
import { getDb } from "../db.js";
|
|
5
4
|
import { memorySegments } from "../schema.js";
|
|
6
5
|
import { computeRecencyScore } from "./ranking.js";
|
|
7
6
|
import type { Candidate, CandidateType } from "./types.js";
|
|
8
7
|
|
|
9
|
-
const log = getLogger("memory-retriever");
|
|
10
|
-
|
|
11
|
-
// Threshold beyond which a raw SQL query is considered slow and logged as a warning.
|
|
12
|
-
const SLOW_QUERY_MS = 2000;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Execute a synchronous raw SQL query with timing. Logs a warning if the
|
|
16
|
-
* query exceeds SLOW_QUERY_MS. Since bun:sqlite queries are synchronous,
|
|
17
|
-
* we can't abort them mid-execution, but we can detect and report slowness.
|
|
18
|
-
*/
|
|
19
|
-
function timedRawQuery<T>(label: string, fn: () => T[]): T[] {
|
|
20
|
-
const start = performance.now();
|
|
21
|
-
const result = fn();
|
|
22
|
-
const elapsed = performance.now() - start;
|
|
23
|
-
if (elapsed >= SLOW_QUERY_MS) {
|
|
24
|
-
log.warn(
|
|
25
|
-
{ label, elapsedMs: Math.round(elapsed) },
|
|
26
|
-
"Raw SQL query exceeded slow threshold",
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface FtsRow {
|
|
33
|
-
segment_id: string;
|
|
34
|
-
message_id: string;
|
|
35
|
-
text: string;
|
|
36
|
-
created_at: number;
|
|
37
|
-
rank: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function lexicalSearch(
|
|
41
|
-
query: string,
|
|
42
|
-
limit: number,
|
|
43
|
-
excludedMessageIds: string[] = [],
|
|
44
|
-
scopeIds?: string[],
|
|
45
|
-
expandedQuery?: string,
|
|
46
|
-
): Candidate[] {
|
|
47
|
-
const trimmed = query.trim();
|
|
48
|
-
if (trimmed.length === 0 || limit <= 0) return [];
|
|
49
|
-
// When an expanded query is provided (e.g. from query expansion in degraded
|
|
50
|
-
// mode), use it instead of building one from the raw conversational query.
|
|
51
|
-
const matchQuery = expandedQuery ?? buildFtsMatchQuery(trimmed);
|
|
52
|
-
if (!matchQuery) return [];
|
|
53
|
-
const excluded = new Set(excludedMessageIds);
|
|
54
|
-
const scopeClause = scopeIds
|
|
55
|
-
? ` AND s.scope_id IN (${scopeIds.map(() => "?").join(",")})`
|
|
56
|
-
: "";
|
|
57
|
-
|
|
58
|
-
// Adaptive overfetch: when exclusions are present, double the SQL LIMIT until we
|
|
59
|
-
// collect `limit` visible rows or the DB has no more matching results to offer.
|
|
60
|
-
// This handles dense exclusion sets without wasteful fixed-ratio over-fetching.
|
|
61
|
-
const MAX_FETCH_MULTIPLIER = 8;
|
|
62
|
-
let queryLimit = excluded.size > 0 ? Math.max(limit * 2, limit + 24) : limit;
|
|
63
|
-
let visibleRows: FtsRow[] = [];
|
|
64
|
-
|
|
65
|
-
while (true) {
|
|
66
|
-
let rows: FtsRow[] = [];
|
|
67
|
-
try {
|
|
68
|
-
rows = timedRawQuery("lexicalSearch", () =>
|
|
69
|
-
rawAll<FtsRow>(
|
|
70
|
-
`
|
|
71
|
-
SELECT
|
|
72
|
-
f.segment_id AS segment_id,
|
|
73
|
-
s.message_id AS message_id,
|
|
74
|
-
s.text AS text,
|
|
75
|
-
s.created_at AS created_at,
|
|
76
|
-
bm25(memory_segment_fts) AS rank
|
|
77
|
-
FROM memory_segment_fts f
|
|
78
|
-
JOIN memory_segments s ON s.id = f.segment_id
|
|
79
|
-
WHERE memory_segment_fts MATCH ?${scopeClause}
|
|
80
|
-
ORDER BY rank
|
|
81
|
-
LIMIT ?
|
|
82
|
-
`,
|
|
83
|
-
matchQuery,
|
|
84
|
-
...(scopeIds ?? []),
|
|
85
|
-
queryLimit,
|
|
86
|
-
),
|
|
87
|
-
);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
log.warn(
|
|
90
|
-
{ err, query: truncate(trimmed, 80) },
|
|
91
|
-
"Memory lexical search query parse failed",
|
|
92
|
-
);
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
visibleRows =
|
|
97
|
-
excluded.size > 0
|
|
98
|
-
? rows.filter((row) => !excluded.has(row.message_id))
|
|
99
|
-
: rows;
|
|
100
|
-
|
|
101
|
-
// Stop when we have enough rows, the DB returned fewer than requested
|
|
102
|
-
// (no more results exist), or we've reached the safety cap.
|
|
103
|
-
if (
|
|
104
|
-
visibleRows.length >= limit ||
|
|
105
|
-
rows.length < queryLimit ||
|
|
106
|
-
queryLimit >= limit * MAX_FETCH_MULTIPLIER
|
|
107
|
-
) {
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
queryLimit = Math.min(queryLimit * 2, limit * MAX_FETCH_MULTIPLIER);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
visibleRows = visibleRows.slice(0, limit);
|
|
114
|
-
|
|
115
|
-
const finiteRanks = visibleRows
|
|
116
|
-
.map((row) => row.rank)
|
|
117
|
-
.filter((rank) => Number.isFinite(rank));
|
|
118
|
-
const minRank = finiteRanks.length > 0 ? Math.min(...finiteRanks) : 0;
|
|
119
|
-
const maxRank = finiteRanks.length > 0 ? Math.max(...finiteRanks) : 0;
|
|
120
|
-
|
|
121
|
-
return visibleRows.map((row) => {
|
|
122
|
-
const lexical = lexicalRankToScore(row.rank, minRank, maxRank);
|
|
123
|
-
return {
|
|
124
|
-
key: `segment:${row.segment_id}`,
|
|
125
|
-
type: "segment" as CandidateType,
|
|
126
|
-
id: row.segment_id,
|
|
127
|
-
source: "lexical",
|
|
128
|
-
text: row.text,
|
|
129
|
-
kind: "segment",
|
|
130
|
-
confidence: 0.55,
|
|
131
|
-
importance: 0.5,
|
|
132
|
-
createdAt: row.created_at,
|
|
133
|
-
lexical,
|
|
134
|
-
semantic: 0,
|
|
135
|
-
recency: computeRecencyScore(row.created_at),
|
|
136
|
-
finalScore: 0,
|
|
137
|
-
};
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
8
|
export function recencySearch(
|
|
142
9
|
conversationId: string,
|
|
143
10
|
limit: number,
|
|
@@ -172,128 +39,8 @@ export function recencySearch(
|
|
|
172
39
|
confidence: 0.55,
|
|
173
40
|
importance: 0.5,
|
|
174
41
|
createdAt: row.createdAt,
|
|
175
|
-
lexical: 0,
|
|
176
42
|
semantic: 0,
|
|
177
43
|
recency: computeRecencyScore(row.createdAt),
|
|
178
44
|
finalScore: 0,
|
|
179
45
|
}));
|
|
180
46
|
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Direct search over memory_items table by subject and statement text.
|
|
184
|
-
* Supplements FTS-based lexical search with LIKE-based matching on items.
|
|
185
|
-
*/
|
|
186
|
-
export function directItemSearch(
|
|
187
|
-
query: string,
|
|
188
|
-
limit: number,
|
|
189
|
-
scopeIds?: string[],
|
|
190
|
-
): Candidate[] {
|
|
191
|
-
const tokens = [
|
|
192
|
-
...new Set(
|
|
193
|
-
query
|
|
194
|
-
.toLowerCase()
|
|
195
|
-
.split(/[^a-z0-9_.-]+/g)
|
|
196
|
-
.filter((t) => t.length >= 2),
|
|
197
|
-
),
|
|
198
|
-
];
|
|
199
|
-
if (tokens.length === 0) return [];
|
|
200
|
-
|
|
201
|
-
const likeClauses = tokens.map(
|
|
202
|
-
() => `(LOWER(subject) LIKE ? OR LOWER(statement) LIKE ?)`,
|
|
203
|
-
);
|
|
204
|
-
const likeParams = tokens.flatMap((t) => {
|
|
205
|
-
const pattern = `%${escapeLikeWildcards(t)}%`;
|
|
206
|
-
return [pattern, pattern];
|
|
207
|
-
});
|
|
208
|
-
const scopeClause = scopeIds
|
|
209
|
-
? ` AND scope_id IN (${scopeIds.map(() => "?").join(",")})`
|
|
210
|
-
: "";
|
|
211
|
-
const sqlQuery = `
|
|
212
|
-
SELECT id, kind, subject, statement, status, confidence, importance, first_seen_at, last_seen_at
|
|
213
|
-
FROM memory_items
|
|
214
|
-
WHERE status = 'active' AND invalid_at IS NULL AND (${likeClauses.join(
|
|
215
|
-
" OR ",
|
|
216
|
-
)})${scopeClause}
|
|
217
|
-
ORDER BY last_seen_at DESC
|
|
218
|
-
LIMIT ?
|
|
219
|
-
`;
|
|
220
|
-
|
|
221
|
-
interface ItemRow {
|
|
222
|
-
id: string;
|
|
223
|
-
kind: string;
|
|
224
|
-
subject: string;
|
|
225
|
-
statement: string;
|
|
226
|
-
confidence: number;
|
|
227
|
-
importance: number | null;
|
|
228
|
-
first_seen_at: number;
|
|
229
|
-
last_seen_at: number;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let rows: ItemRow[] = [];
|
|
233
|
-
try {
|
|
234
|
-
rows = timedRawQuery("directItemSearch", () =>
|
|
235
|
-
rawAll<ItemRow>(sqlQuery, ...likeParams, ...(scopeIds ?? []), limit),
|
|
236
|
-
);
|
|
237
|
-
} catch {
|
|
238
|
-
return [];
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return rows.map((row) => {
|
|
242
|
-
// Compute lexical score based on token match coverage: fraction of query
|
|
243
|
-
// tokens that appear in subject or statement. Direct items are keyword
|
|
244
|
-
// matches so this score reflects query-match relevance (unlike confidence,
|
|
245
|
-
// which reflects extraction certainty).
|
|
246
|
-
const textLower = `${row.subject} ${row.statement}`.toLowerCase();
|
|
247
|
-
const matchedTokens = tokens.filter((t) => textLower.includes(t)).length;
|
|
248
|
-
const lexical = tokens.length > 0 ? matchedTokens / tokens.length : 0;
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
key: `item:${row.id}`,
|
|
252
|
-
type: "item" as CandidateType,
|
|
253
|
-
id: row.id,
|
|
254
|
-
source: "item_direct",
|
|
255
|
-
text: `${row.subject}: ${row.statement}`,
|
|
256
|
-
kind: row.kind,
|
|
257
|
-
confidence: row.confidence,
|
|
258
|
-
importance: row.importance ?? 0.5,
|
|
259
|
-
createdAt: row.last_seen_at,
|
|
260
|
-
lexical,
|
|
261
|
-
semantic: 0,
|
|
262
|
-
recency: computeRecencyScore(row.last_seen_at),
|
|
263
|
-
finalScore: 0,
|
|
264
|
-
};
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
export function lexicalRankToScore(
|
|
269
|
-
rank: number,
|
|
270
|
-
minRank: number,
|
|
271
|
-
maxRank: number,
|
|
272
|
-
): number {
|
|
273
|
-
if (!Number.isFinite(rank)) return 0;
|
|
274
|
-
if (!Number.isFinite(minRank) || !Number.isFinite(maxRank)) return 0;
|
|
275
|
-
const span = maxRank - minRank;
|
|
276
|
-
if (span <= 0) return 1;
|
|
277
|
-
// Lower BM25 rank is better in FTS5; normalize to [0,1] where 1 is best.
|
|
278
|
-
return (maxRank - rank) / span;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export function buildFtsMatchQuery(text: string): string | null {
|
|
282
|
-
const tokens = text
|
|
283
|
-
.toLowerCase()
|
|
284
|
-
.split(/[^a-z0-9_]+/g)
|
|
285
|
-
.map((token) => token.trim())
|
|
286
|
-
.filter((token) => token.length >= 2);
|
|
287
|
-
if (tokens.length === 0) return null;
|
|
288
|
-
const unique = [...new Set(tokens)].slice(0, 24);
|
|
289
|
-
return unique.map((token) => `"${token.replace(/"/g, '""')}"`).join(" OR ");
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export function escapeLikeWildcards(s: string): string {
|
|
293
|
-
return s.replace(/%/g, "").replace(/_/g, "");
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function truncate(text: string, max: number): string {
|
|
297
|
-
if (text.length <= max) return text;
|
|
298
|
-
return `${text.slice(0, max - 3)}...`;
|
|
299
|
-
}
|