@anuma/sdk 5.0.0 → 5.0.2
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/dist/expo/{consolidate-KASO7JDS.mjs → consolidate-T6GZ5YQ4.mjs} +8 -6
- package/dist/expo/index.cjs +78 -17
- package/dist/expo/index.d.mts +6 -1
- package/dist/expo/index.d.ts +6 -1
- package/dist/expo/index.mjs +71 -12
- package/dist/react/{consolidate-IFIKT25Z.mjs → consolidate-POPTUI54.mjs} +8 -6
- package/dist/react/index.cjs +78 -17
- package/dist/react/index.d.mts +6 -1
- package/dist/react/index.d.ts +6 -1
- package/dist/react/index.mjs +71 -12
- package/dist/server/{consolidate-GRMEDWKK.mjs → consolidate-LMIDJFR6.mjs} +8 -6
- package/dist/server/index.cjs +78 -17
- package/dist/server/index.d.mts +6 -1
- package/dist/server/index.d.ts +6 -1
- package/dist/server/index.mjs +71 -12
- package/package.json +3 -2
|
@@ -55,12 +55,14 @@ async function consolidateMemory(newContent, candidates, options) {
|
|
|
55
55
|
if (trimmed.length === 0) return fallback;
|
|
56
56
|
if (candidates.length === 0) return fallback;
|
|
57
57
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
58
|
-
const safeTrimmed = redactor ? redactor.
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
58
|
+
const safeTrimmed = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
59
|
+
const rows = [];
|
|
60
|
+
for (const [i, c] of candidates.entries()) {
|
|
61
|
+
const safeContent = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
62
|
+
rows.push(`[${i + 1}] (id: ${c.id}, sim: ${c.similarity.toFixed(2)})
|
|
63
|
+
${safeContent}`);
|
|
64
|
+
}
|
|
65
|
+
const candidateText = rows.join("\n");
|
|
64
66
|
const userMessage = `New memory:
|
|
65
67
|
${safeTrimmed}
|
|
66
68
|
|
package/dist/expo/index.cjs
CHANGED
|
@@ -8755,12 +8755,14 @@ async function consolidateMemory(newContent, candidates, options) {
|
|
|
8755
8755
|
if (trimmed.length === 0) return fallback;
|
|
8756
8756
|
if (candidates.length === 0) return fallback;
|
|
8757
8757
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
8758
|
-
const safeTrimmed = redactor ? redactor.
|
|
8759
|
-
const
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
})
|
|
8758
|
+
const safeTrimmed = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
8759
|
+
const rows = [];
|
|
8760
|
+
for (const [i, c] of candidates.entries()) {
|
|
8761
|
+
const safeContent = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
8762
|
+
rows.push(`[${i + 1}] (id: ${c.id}, sim: ${c.similarity.toFixed(2)})
|
|
8763
|
+
${safeContent}`);
|
|
8764
|
+
}
|
|
8765
|
+
const candidateText = rows.join("\n");
|
|
8764
8766
|
const userMessage = `New memory:
|
|
8765
8767
|
${safeTrimmed}
|
|
8766
8768
|
|
|
@@ -15190,10 +15192,12 @@ async function classifyInjectionCandidates(candidates, options) {
|
|
|
15190
15192
|
);
|
|
15191
15193
|
}
|
|
15192
15194
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
15193
|
-
const
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15195
|
+
const lines = [];
|
|
15196
|
+
for (const [i, c] of scope.entries()) {
|
|
15197
|
+
const safe = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
15198
|
+
lines.push(`[${i + 1}] ${safe}`);
|
|
15199
|
+
}
|
|
15200
|
+
const numbered = lines.join("\n");
|
|
15197
15201
|
let parsed;
|
|
15198
15202
|
try {
|
|
15199
15203
|
parsed = await callPortalJsonCompletion({
|
|
@@ -16515,7 +16519,7 @@ function createLlmDecayClassifier(options) {
|
|
|
16515
16519
|
}
|
|
16516
16520
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
16517
16521
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
16518
|
-
const safe = redactor ? redactor.
|
|
16522
|
+
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
16519
16523
|
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input.updatedAt, now)}`;
|
|
16520
16524
|
let parsed;
|
|
16521
16525
|
try {
|
|
@@ -17645,17 +17649,22 @@ async function extractEntitiesForMemories(memories, options) {
|
|
|
17645
17649
|
const out = /* @__PURE__ */ new Map();
|
|
17646
17650
|
if (memories.length === 0) return out;
|
|
17647
17651
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
17648
|
-
const vocabulary =
|
|
17652
|
+
const vocabulary = [];
|
|
17653
|
+
for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
|
|
17654
|
+
vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
|
|
17655
|
+
}
|
|
17649
17656
|
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
17650
17657
|
When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
|
|
17651
17658
|
|
|
17652
17659
|
` : "";
|
|
17653
17660
|
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
17654
17661
|
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
17655
|
-
const
|
|
17662
|
+
const rows = [];
|
|
17663
|
+
for (const m of batch) {
|
|
17656
17664
|
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
|
|
17657
|
-
|
|
17658
|
-
}
|
|
17665
|
+
rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
|
|
17666
|
+
}
|
|
17667
|
+
const listing = rows.join("\n");
|
|
17659
17668
|
const parsed = await callPortalJsonCompletion({
|
|
17660
17669
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
17661
17670
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
@@ -21056,7 +21065,7 @@ __decorateClass([
|
|
|
21056
21065
|
], VaultFolder.prototype, "context", 2);
|
|
21057
21066
|
|
|
21058
21067
|
// src/lib/db/schema.ts
|
|
21059
|
-
var SDK_SCHEMA_VERSION =
|
|
21068
|
+
var SDK_SCHEMA_VERSION = 43;
|
|
21060
21069
|
var sdkSchema = (0, import_watermelondb19.appSchema)({
|
|
21061
21070
|
version: SDK_SCHEMA_VERSION,
|
|
21062
21071
|
tables: [
|
|
@@ -21104,7 +21113,16 @@ var sdkSchema = (0, import_watermelondb19.appSchema)({
|
|
|
21104
21113
|
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
21105
21114
|
{ name: "title", type: "string" },
|
|
21106
21115
|
{ name: "project_id", type: "string", isOptional: true, isIndexed: true },
|
|
21107
|
-
|
|
21116
|
+
// Indexed to match every other list-sorted created_at in this schema.
|
|
21117
|
+
// Note this single-column index is NOT what makes the conversation list
|
|
21118
|
+
// reads fast on native SQLite — they all filter is_deleted as well, and
|
|
21119
|
+
// SQLite will not combine the two indexes, so the composite created by
|
|
21120
|
+
// the v42 migration is what its planner actually uses. This declaration
|
|
21121
|
+
// still earns its keep on the other two adapters: LokiJS builds its
|
|
21122
|
+
// binary indices from `isIndexed` (and ignores `sql` migration steps
|
|
21123
|
+
// outright), and Postgres gathers statistics via autovacuum, so its
|
|
21124
|
+
// planner can use a single-column index the SQLite planner skips.
|
|
21125
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
21108
21126
|
{ name: "updated_at", type: "number" },
|
|
21109
21127
|
{ name: "is_deleted", type: "boolean", isIndexed: true },
|
|
21110
21128
|
{ name: "pinned_at", type: "number", isOptional: true }
|
|
@@ -21971,6 +21989,49 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
|
|
|
21971
21989
|
]
|
|
21972
21990
|
})
|
|
21973
21991
|
]
|
|
21992
|
+
},
|
|
21993
|
+
// v42 -> v43: Give the conversation list reads an index they can actually
|
|
21994
|
+
// sort on. All five of them (getConversationsOp, getConversationsLazyOp,
|
|
21995
|
+
// getConversationsByProjectOp, getConversationsByProjectLazyOp and the
|
|
21996
|
+
// keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
|
|
21997
|
+
// created_at desc`, so before this they resolved is_deleted through its
|
|
21998
|
+
// index and then built a temp B-tree over every live row just to return
|
|
21999
|
+
// the newest page.
|
|
22000
|
+
//
|
|
22001
|
+
// The index is COMPOSITE, and that is the whole point — a bare index on
|
|
22002
|
+
// created_at does not fix this. SQLite will not combine two single-column
|
|
22003
|
+
// indexes here, so with `is_deleted` indexed and `created_at` indexed
|
|
22004
|
+
// separately it still picks conversations_is_deleted and still temp-sorts.
|
|
22005
|
+
// It only prefers a lone created_at index once ANALYZE has populated
|
|
22006
|
+
// sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
|
|
22007
|
+
// native SQLite bindings, not this SDK. Every device is permanently in the
|
|
22008
|
+
// no-statistics state, so the index has to satisfy the filter and the sort
|
|
22009
|
+
// in one structure. `(is_deleted, created_at)` does: equality on the
|
|
22010
|
+
// leading column, ordered scan on the second, and the keyset boundary
|
|
22011
|
+
// becomes a range seek on the same index.
|
|
22012
|
+
//
|
|
22013
|
+
// unsafeExecuteSql, not addColumns: both columns already exist and
|
|
22014
|
+
// WatermelonDB has no add-index migration step, so raw SQL is the only way
|
|
22015
|
+
// to build one in place (same shape as the v19 -> v20 memory_vault
|
|
22016
|
+
// migration). IF NOT EXISTS keeps the step idempotent.
|
|
22017
|
+
//
|
|
22018
|
+
// Two limits worth knowing before someone re-measures this and finds it
|
|
22019
|
+
// missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
|
|
22020
|
+
// so existing browser databases gain nothing — web's only lever is the
|
|
22021
|
+
// `isIndexed` flag on the column itself. Second, this index reaches
|
|
22022
|
+
// MIGRATED databases only: WatermelonDB builds a fresh database purely from
|
|
22023
|
+
// the encoded schema, and its schema format cannot express a composite
|
|
22024
|
+
// index. The one hook that could (`unsafeSql` on the table) is a function,
|
|
22025
|
+
// and the LokiJS adapter posts the schema to its worker, so attaching it
|
|
22026
|
+
// makes the whole schema fail structuredClone and takes the database down
|
|
22027
|
+
// at setup for anyone on `useWebWorker: true`. Not worth it for an index.
|
|
22028
|
+
{
|
|
22029
|
+
toVersion: 43,
|
|
22030
|
+
steps: [
|
|
22031
|
+
(0, import_migrations2.unsafeExecuteSql)(
|
|
22032
|
+
"CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
|
|
22033
|
+
)
|
|
22034
|
+
]
|
|
21974
22035
|
}
|
|
21975
22036
|
]
|
|
21976
22037
|
});
|
package/dist/expo/index.d.mts
CHANGED
|
@@ -8779,8 +8779,12 @@ declare function useChatStorage(options: UseChatStorageOptions): UseChatStorageR
|
|
|
8779
8779
|
* `topics_updated_at` is a SECOND timestamp because every topic writer pins
|
|
8780
8780
|
* `updated_at` on purpose (recall's recency multiplier) and both sync paths
|
|
8781
8781
|
* key on `updated_at`, so a topic-only change would neither upload nor merge
|
|
8782
|
+
* - v43: Added a (is_deleted, created_at) index to conversations. Every
|
|
8783
|
+
* conversation list read filters is_deleted and orders by created_at DESC,
|
|
8784
|
+
* which previously meant a temp B-tree sort of the whole live set on every
|
|
8785
|
+
* read. Structural only — no column added, no data rewritten
|
|
8782
8786
|
*/
|
|
8783
|
-
declare const SDK_SCHEMA_VERSION =
|
|
8787
|
+
declare const SDK_SCHEMA_VERSION = 43;
|
|
8784
8788
|
/**
|
|
8785
8789
|
* Combined WatermelonDB schema for all SDK storage modules.
|
|
8786
8790
|
*
|
|
@@ -8867,6 +8871,7 @@ declare const sdkSchema: Readonly<{
|
|
|
8867
8871
|
* - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
|
|
8868
8872
|
* - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
|
|
8869
8873
|
* - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
|
|
8874
|
+
* - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
|
|
8870
8875
|
*/
|
|
8871
8876
|
declare const sdkMigrations: Readonly<{
|
|
8872
8877
|
validated: true;
|
package/dist/expo/index.d.ts
CHANGED
|
@@ -8779,8 +8779,12 @@ declare function useChatStorage(options: UseChatStorageOptions): UseChatStorageR
|
|
|
8779
8779
|
* `topics_updated_at` is a SECOND timestamp because every topic writer pins
|
|
8780
8780
|
* `updated_at` on purpose (recall's recency multiplier) and both sync paths
|
|
8781
8781
|
* key on `updated_at`, so a topic-only change would neither upload nor merge
|
|
8782
|
+
* - v43: Added a (is_deleted, created_at) index to conversations. Every
|
|
8783
|
+
* conversation list read filters is_deleted and orders by created_at DESC,
|
|
8784
|
+
* which previously meant a temp B-tree sort of the whole live set on every
|
|
8785
|
+
* read. Structural only — no column added, no data rewritten
|
|
8782
8786
|
*/
|
|
8783
|
-
declare const SDK_SCHEMA_VERSION =
|
|
8787
|
+
declare const SDK_SCHEMA_VERSION = 43;
|
|
8784
8788
|
/**
|
|
8785
8789
|
* Combined WatermelonDB schema for all SDK storage modules.
|
|
8786
8790
|
*
|
|
@@ -8867,6 +8871,7 @@ declare const sdkSchema: Readonly<{
|
|
|
8867
8871
|
* - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
|
|
8868
8872
|
* - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
|
|
8869
8873
|
* - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
|
|
8874
|
+
* - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
|
|
8870
8875
|
*/
|
|
8871
8876
|
declare const sdkMigrations: Readonly<{
|
|
8872
8877
|
validated: true;
|
package/dist/expo/index.mjs
CHANGED
|
@@ -6193,10 +6193,12 @@ async function classifyInjectionCandidates(candidates, options) {
|
|
|
6193
6193
|
);
|
|
6194
6194
|
}
|
|
6195
6195
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
6196
|
-
const
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6196
|
+
const lines = [];
|
|
6197
|
+
for (const [i, c] of scope.entries()) {
|
|
6198
|
+
const safe = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
6199
|
+
lines.push(`[${i + 1}] ${safe}`);
|
|
6200
|
+
}
|
|
6201
|
+
const numbered = lines.join("\n");
|
|
6200
6202
|
let parsed;
|
|
6201
6203
|
try {
|
|
6202
6204
|
parsed = await callPortalJsonCompletion({
|
|
@@ -6677,7 +6679,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
6677
6679
|
content: m.content,
|
|
6678
6680
|
similarity: m.similarity
|
|
6679
6681
|
}));
|
|
6680
|
-
const { consolidateMemory: doConsolidate } = await import("./consolidate-
|
|
6682
|
+
const { consolidateMemory: doConsolidate } = await import("./consolidate-T6GZ5YQ4.mjs");
|
|
6681
6683
|
const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
|
|
6682
6684
|
if (decision.action === "create") return null;
|
|
6683
6685
|
if (decision.action === "supersede" && decision.content) {
|
|
@@ -7501,7 +7503,7 @@ function createLlmDecayClassifier(options) {
|
|
|
7501
7503
|
}
|
|
7502
7504
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
7503
7505
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
7504
|
-
const safe = redactor ? redactor.
|
|
7506
|
+
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
7505
7507
|
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input.updatedAt, now)}`;
|
|
7506
7508
|
let parsed;
|
|
7507
7509
|
try {
|
|
@@ -8598,17 +8600,22 @@ async function extractEntitiesForMemories(memories, options) {
|
|
|
8598
8600
|
const out = /* @__PURE__ */ new Map();
|
|
8599
8601
|
if (memories.length === 0) return out;
|
|
8600
8602
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
8601
|
-
const vocabulary =
|
|
8603
|
+
const vocabulary = [];
|
|
8604
|
+
for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
|
|
8605
|
+
vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
|
|
8606
|
+
}
|
|
8602
8607
|
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
8603
8608
|
When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
|
|
8604
8609
|
|
|
8605
8610
|
` : "";
|
|
8606
8611
|
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
8607
8612
|
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
8608
|
-
const
|
|
8613
|
+
const rows = [];
|
|
8614
|
+
for (const m of batch) {
|
|
8609
8615
|
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
|
|
8610
|
-
|
|
8611
|
-
}
|
|
8616
|
+
rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
|
|
8617
|
+
}
|
|
8618
|
+
const listing = rows.join("\n");
|
|
8612
8619
|
const parsed = await callPortalJsonCompletion({
|
|
8613
8620
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
8614
8621
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
@@ -11980,7 +11987,7 @@ __decorateClass([
|
|
|
11980
11987
|
], VaultFolder.prototype, "context", 2);
|
|
11981
11988
|
|
|
11982
11989
|
// src/lib/db/schema.ts
|
|
11983
|
-
var SDK_SCHEMA_VERSION =
|
|
11990
|
+
var SDK_SCHEMA_VERSION = 43;
|
|
11984
11991
|
var sdkSchema = appSchema2({
|
|
11985
11992
|
version: SDK_SCHEMA_VERSION,
|
|
11986
11993
|
tables: [
|
|
@@ -12028,7 +12035,16 @@ var sdkSchema = appSchema2({
|
|
|
12028
12035
|
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
12029
12036
|
{ name: "title", type: "string" },
|
|
12030
12037
|
{ name: "project_id", type: "string", isOptional: true, isIndexed: true },
|
|
12031
|
-
|
|
12038
|
+
// Indexed to match every other list-sorted created_at in this schema.
|
|
12039
|
+
// Note this single-column index is NOT what makes the conversation list
|
|
12040
|
+
// reads fast on native SQLite — they all filter is_deleted as well, and
|
|
12041
|
+
// SQLite will not combine the two indexes, so the composite created by
|
|
12042
|
+
// the v42 migration is what its planner actually uses. This declaration
|
|
12043
|
+
// still earns its keep on the other two adapters: LokiJS builds its
|
|
12044
|
+
// binary indices from `isIndexed` (and ignores `sql` migration steps
|
|
12045
|
+
// outright), and Postgres gathers statistics via autovacuum, so its
|
|
12046
|
+
// planner can use a single-column index the SQLite planner skips.
|
|
12047
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
12032
12048
|
{ name: "updated_at", type: "number" },
|
|
12033
12049
|
{ name: "is_deleted", type: "boolean", isIndexed: true },
|
|
12034
12050
|
{ name: "pinned_at", type: "number", isOptional: true }
|
|
@@ -12895,6 +12911,49 @@ var sdkMigrations = schemaMigrations2({
|
|
|
12895
12911
|
]
|
|
12896
12912
|
})
|
|
12897
12913
|
]
|
|
12914
|
+
},
|
|
12915
|
+
// v42 -> v43: Give the conversation list reads an index they can actually
|
|
12916
|
+
// sort on. All five of them (getConversationsOp, getConversationsLazyOp,
|
|
12917
|
+
// getConversationsByProjectOp, getConversationsByProjectLazyOp and the
|
|
12918
|
+
// keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
|
|
12919
|
+
// created_at desc`, so before this they resolved is_deleted through its
|
|
12920
|
+
// index and then built a temp B-tree over every live row just to return
|
|
12921
|
+
// the newest page.
|
|
12922
|
+
//
|
|
12923
|
+
// The index is COMPOSITE, and that is the whole point — a bare index on
|
|
12924
|
+
// created_at does not fix this. SQLite will not combine two single-column
|
|
12925
|
+
// indexes here, so with `is_deleted` indexed and `created_at` indexed
|
|
12926
|
+
// separately it still picks conversations_is_deleted and still temp-sorts.
|
|
12927
|
+
// It only prefers a lone created_at index once ANALYZE has populated
|
|
12928
|
+
// sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
|
|
12929
|
+
// native SQLite bindings, not this SDK. Every device is permanently in the
|
|
12930
|
+
// no-statistics state, so the index has to satisfy the filter and the sort
|
|
12931
|
+
// in one structure. `(is_deleted, created_at)` does: equality on the
|
|
12932
|
+
// leading column, ordered scan on the second, and the keyset boundary
|
|
12933
|
+
// becomes a range seek on the same index.
|
|
12934
|
+
//
|
|
12935
|
+
// unsafeExecuteSql, not addColumns: both columns already exist and
|
|
12936
|
+
// WatermelonDB has no add-index migration step, so raw SQL is the only way
|
|
12937
|
+
// to build one in place (same shape as the v19 -> v20 memory_vault
|
|
12938
|
+
// migration). IF NOT EXISTS keeps the step idempotent.
|
|
12939
|
+
//
|
|
12940
|
+
// Two limits worth knowing before someone re-measures this and finds it
|
|
12941
|
+
// missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
|
|
12942
|
+
// so existing browser databases gain nothing — web's only lever is the
|
|
12943
|
+
// `isIndexed` flag on the column itself. Second, this index reaches
|
|
12944
|
+
// MIGRATED databases only: WatermelonDB builds a fresh database purely from
|
|
12945
|
+
// the encoded schema, and its schema format cannot express a composite
|
|
12946
|
+
// index. The one hook that could (`unsafeSql` on the table) is a function,
|
|
12947
|
+
// and the LokiJS adapter posts the schema to its worker, so attaching it
|
|
12948
|
+
// makes the whole schema fail structuredClone and takes the database down
|
|
12949
|
+
// at setup for anyone on `useWebWorker: true`. Not worth it for an index.
|
|
12950
|
+
{
|
|
12951
|
+
toVersion: 43,
|
|
12952
|
+
steps: [
|
|
12953
|
+
unsafeExecuteSql(
|
|
12954
|
+
"CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
|
|
12955
|
+
)
|
|
12956
|
+
]
|
|
12898
12957
|
}
|
|
12899
12958
|
]
|
|
12900
12959
|
});
|
|
@@ -55,12 +55,14 @@ async function consolidateMemory(newContent, candidates, options) {
|
|
|
55
55
|
if (trimmed.length === 0) return fallback;
|
|
56
56
|
if (candidates.length === 0) return fallback;
|
|
57
57
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
58
|
-
const safeTrimmed = redactor ? redactor.
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
58
|
+
const safeTrimmed = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
59
|
+
const rows = [];
|
|
60
|
+
for (const [i, c] of candidates.entries()) {
|
|
61
|
+
const safeContent = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
62
|
+
rows.push(`[${i + 1}] (id: ${c.id}, sim: ${c.similarity.toFixed(2)})
|
|
63
|
+
${safeContent}`);
|
|
64
|
+
}
|
|
65
|
+
const candidateText = rows.join("\n");
|
|
64
66
|
const userMessage = `New memory:
|
|
65
67
|
${safeTrimmed}
|
|
66
68
|
|
package/dist/react/index.cjs
CHANGED
|
@@ -9038,12 +9038,14 @@ async function consolidateMemory(newContent, candidates, options) {
|
|
|
9038
9038
|
if (trimmed.length === 0) return fallback;
|
|
9039
9039
|
if (candidates.length === 0) return fallback;
|
|
9040
9040
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
9041
|
-
const safeTrimmed = redactor ? redactor.
|
|
9042
|
-
const
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
})
|
|
9041
|
+
const safeTrimmed = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
9042
|
+
const rows = [];
|
|
9043
|
+
for (const [i, c] of candidates.entries()) {
|
|
9044
|
+
const safeContent = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
9045
|
+
rows.push(`[${i + 1}] (id: ${c.id}, sim: ${c.similarity.toFixed(2)})
|
|
9046
|
+
${safeContent}`);
|
|
9047
|
+
}
|
|
9048
|
+
const candidateText = rows.join("\n");
|
|
9047
9049
|
const userMessage = `New memory:
|
|
9048
9050
|
${safeTrimmed}
|
|
9049
9051
|
|
|
@@ -16489,10 +16491,12 @@ async function classifyInjectionCandidates(candidates, options) {
|
|
|
16489
16491
|
);
|
|
16490
16492
|
}
|
|
16491
16493
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
16492
|
-
const
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16494
|
+
const lines = [];
|
|
16495
|
+
for (const [i, c] of scope.entries()) {
|
|
16496
|
+
const safe = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
16497
|
+
lines.push(`[${i + 1}] ${safe}`);
|
|
16498
|
+
}
|
|
16499
|
+
const numbered = lines.join("\n");
|
|
16496
16500
|
let parsed;
|
|
16497
16501
|
try {
|
|
16498
16502
|
parsed = await callPortalJsonCompletion({
|
|
@@ -17814,7 +17818,7 @@ function createLlmDecayClassifier(options) {
|
|
|
17814
17818
|
}
|
|
17815
17819
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
17816
17820
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
17817
|
-
const safe = redactor ? redactor.
|
|
17821
|
+
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
17818
17822
|
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input.updatedAt, now)}`;
|
|
17819
17823
|
let parsed;
|
|
17820
17824
|
try {
|
|
@@ -18944,17 +18948,22 @@ async function extractEntitiesForMemories(memories, options) {
|
|
|
18944
18948
|
const out = /* @__PURE__ */ new Map();
|
|
18945
18949
|
if (memories.length === 0) return out;
|
|
18946
18950
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
18947
|
-
const vocabulary =
|
|
18951
|
+
const vocabulary = [];
|
|
18952
|
+
for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
|
|
18953
|
+
vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
|
|
18954
|
+
}
|
|
18948
18955
|
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
18949
18956
|
When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
|
|
18950
18957
|
|
|
18951
18958
|
` : "";
|
|
18952
18959
|
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
18953
18960
|
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
18954
|
-
const
|
|
18961
|
+
const rows = [];
|
|
18962
|
+
for (const m of batch) {
|
|
18955
18963
|
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
|
|
18956
|
-
|
|
18957
|
-
}
|
|
18964
|
+
rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
|
|
18965
|
+
}
|
|
18966
|
+
const listing = rows.join("\n");
|
|
18958
18967
|
const parsed = await callPortalJsonCompletion({
|
|
18959
18968
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
18960
18969
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
@@ -23629,7 +23638,7 @@ __decorateClass([
|
|
|
23629
23638
|
], VaultFolder.prototype, "context", 2);
|
|
23630
23639
|
|
|
23631
23640
|
// src/lib/db/schema.ts
|
|
23632
|
-
var SDK_SCHEMA_VERSION =
|
|
23641
|
+
var SDK_SCHEMA_VERSION = 43;
|
|
23633
23642
|
var sdkSchema = (0, import_watermelondb20.appSchema)({
|
|
23634
23643
|
version: SDK_SCHEMA_VERSION,
|
|
23635
23644
|
tables: [
|
|
@@ -23677,7 +23686,16 @@ var sdkSchema = (0, import_watermelondb20.appSchema)({
|
|
|
23677
23686
|
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
23678
23687
|
{ name: "title", type: "string" },
|
|
23679
23688
|
{ name: "project_id", type: "string", isOptional: true, isIndexed: true },
|
|
23680
|
-
|
|
23689
|
+
// Indexed to match every other list-sorted created_at in this schema.
|
|
23690
|
+
// Note this single-column index is NOT what makes the conversation list
|
|
23691
|
+
// reads fast on native SQLite — they all filter is_deleted as well, and
|
|
23692
|
+
// SQLite will not combine the two indexes, so the composite created by
|
|
23693
|
+
// the v42 migration is what its planner actually uses. This declaration
|
|
23694
|
+
// still earns its keep on the other two adapters: LokiJS builds its
|
|
23695
|
+
// binary indices from `isIndexed` (and ignores `sql` migration steps
|
|
23696
|
+
// outright), and Postgres gathers statistics via autovacuum, so its
|
|
23697
|
+
// planner can use a single-column index the SQLite planner skips.
|
|
23698
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
23681
23699
|
{ name: "updated_at", type: "number" },
|
|
23682
23700
|
{ name: "is_deleted", type: "boolean", isIndexed: true },
|
|
23683
23701
|
{ name: "pinned_at", type: "number", isOptional: true }
|
|
@@ -24544,6 +24562,49 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
|
|
|
24544
24562
|
]
|
|
24545
24563
|
})
|
|
24546
24564
|
]
|
|
24565
|
+
},
|
|
24566
|
+
// v42 -> v43: Give the conversation list reads an index they can actually
|
|
24567
|
+
// sort on. All five of them (getConversationsOp, getConversationsLazyOp,
|
|
24568
|
+
// getConversationsByProjectOp, getConversationsByProjectLazyOp and the
|
|
24569
|
+
// keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
|
|
24570
|
+
// created_at desc`, so before this they resolved is_deleted through its
|
|
24571
|
+
// index and then built a temp B-tree over every live row just to return
|
|
24572
|
+
// the newest page.
|
|
24573
|
+
//
|
|
24574
|
+
// The index is COMPOSITE, and that is the whole point — a bare index on
|
|
24575
|
+
// created_at does not fix this. SQLite will not combine two single-column
|
|
24576
|
+
// indexes here, so with `is_deleted` indexed and `created_at` indexed
|
|
24577
|
+
// separately it still picks conversations_is_deleted and still temp-sorts.
|
|
24578
|
+
// It only prefers a lone created_at index once ANALYZE has populated
|
|
24579
|
+
// sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
|
|
24580
|
+
// native SQLite bindings, not this SDK. Every device is permanently in the
|
|
24581
|
+
// no-statistics state, so the index has to satisfy the filter and the sort
|
|
24582
|
+
// in one structure. `(is_deleted, created_at)` does: equality on the
|
|
24583
|
+
// leading column, ordered scan on the second, and the keyset boundary
|
|
24584
|
+
// becomes a range seek on the same index.
|
|
24585
|
+
//
|
|
24586
|
+
// unsafeExecuteSql, not addColumns: both columns already exist and
|
|
24587
|
+
// WatermelonDB has no add-index migration step, so raw SQL is the only way
|
|
24588
|
+
// to build one in place (same shape as the v19 -> v20 memory_vault
|
|
24589
|
+
// migration). IF NOT EXISTS keeps the step idempotent.
|
|
24590
|
+
//
|
|
24591
|
+
// Two limits worth knowing before someone re-measures this and finds it
|
|
24592
|
+
// missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
|
|
24593
|
+
// so existing browser databases gain nothing — web's only lever is the
|
|
24594
|
+
// `isIndexed` flag on the column itself. Second, this index reaches
|
|
24595
|
+
// MIGRATED databases only: WatermelonDB builds a fresh database purely from
|
|
24596
|
+
// the encoded schema, and its schema format cannot express a composite
|
|
24597
|
+
// index. The one hook that could (`unsafeSql` on the table) is a function,
|
|
24598
|
+
// and the LokiJS adapter posts the schema to its worker, so attaching it
|
|
24599
|
+
// makes the whole schema fail structuredClone and takes the database down
|
|
24600
|
+
// at setup for anyone on `useWebWorker: true`. Not worth it for an index.
|
|
24601
|
+
{
|
|
24602
|
+
toVersion: 43,
|
|
24603
|
+
steps: [
|
|
24604
|
+
(0, import_migrations2.unsafeExecuteSql)(
|
|
24605
|
+
"CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
|
|
24606
|
+
)
|
|
24607
|
+
]
|
|
24547
24608
|
}
|
|
24548
24609
|
]
|
|
24549
24610
|
});
|
package/dist/react/index.d.mts
CHANGED
|
@@ -10393,8 +10393,12 @@ declare class WatermelonChatStorageAdapter implements ChatStorageAdapter {
|
|
|
10393
10393
|
* `topics_updated_at` is a SECOND timestamp because every topic writer pins
|
|
10394
10394
|
* `updated_at` on purpose (recall's recency multiplier) and both sync paths
|
|
10395
10395
|
* key on `updated_at`, so a topic-only change would neither upload nor merge
|
|
10396
|
+
* - v43: Added a (is_deleted, created_at) index to conversations. Every
|
|
10397
|
+
* conversation list read filters is_deleted and orders by created_at DESC,
|
|
10398
|
+
* which previously meant a temp B-tree sort of the whole live set on every
|
|
10399
|
+
* read. Structural only — no column added, no data rewritten
|
|
10396
10400
|
*/
|
|
10397
|
-
declare const SDK_SCHEMA_VERSION =
|
|
10401
|
+
declare const SDK_SCHEMA_VERSION = 43;
|
|
10398
10402
|
/**
|
|
10399
10403
|
* Combined WatermelonDB schema for all SDK storage modules.
|
|
10400
10404
|
*
|
|
@@ -10481,6 +10485,7 @@ declare const sdkSchema: Readonly<{
|
|
|
10481
10485
|
* - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
|
|
10482
10486
|
* - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
|
|
10483
10487
|
* - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
|
|
10488
|
+
* - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
|
|
10484
10489
|
*/
|
|
10485
10490
|
declare const sdkMigrations: Readonly<{
|
|
10486
10491
|
validated: true;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -10393,8 +10393,12 @@ declare class WatermelonChatStorageAdapter implements ChatStorageAdapter {
|
|
|
10393
10393
|
* `topics_updated_at` is a SECOND timestamp because every topic writer pins
|
|
10394
10394
|
* `updated_at` on purpose (recall's recency multiplier) and both sync paths
|
|
10395
10395
|
* key on `updated_at`, so a topic-only change would neither upload nor merge
|
|
10396
|
+
* - v43: Added a (is_deleted, created_at) index to conversations. Every
|
|
10397
|
+
* conversation list read filters is_deleted and orders by created_at DESC,
|
|
10398
|
+
* which previously meant a temp B-tree sort of the whole live set on every
|
|
10399
|
+
* read. Structural only — no column added, no data rewritten
|
|
10396
10400
|
*/
|
|
10397
|
-
declare const SDK_SCHEMA_VERSION =
|
|
10401
|
+
declare const SDK_SCHEMA_VERSION = 43;
|
|
10398
10402
|
/**
|
|
10399
10403
|
* Combined WatermelonDB schema for all SDK storage modules.
|
|
10400
10404
|
*
|
|
@@ -10481,6 +10485,7 @@ declare const sdkSchema: Readonly<{
|
|
|
10481
10485
|
* - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
|
|
10482
10486
|
* - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
|
|
10483
10487
|
* - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
|
|
10488
|
+
* - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
|
|
10484
10489
|
*/
|
|
10485
10490
|
declare const sdkMigrations: Readonly<{
|
|
10486
10491
|
validated: true;
|
package/dist/react/index.mjs
CHANGED
|
@@ -7010,10 +7010,12 @@ async function classifyInjectionCandidates(candidates, options) {
|
|
|
7010
7010
|
);
|
|
7011
7011
|
}
|
|
7012
7012
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
7013
|
-
const
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7013
|
+
const lines = [];
|
|
7014
|
+
for (const [i, c] of scope.entries()) {
|
|
7015
|
+
const safe = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
7016
|
+
lines.push(`[${i + 1}] ${safe}`);
|
|
7017
|
+
}
|
|
7018
|
+
const numbered = lines.join("\n");
|
|
7017
7019
|
let parsed;
|
|
7018
7020
|
try {
|
|
7019
7021
|
parsed = await callPortalJsonCompletion({
|
|
@@ -7494,7 +7496,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
7494
7496
|
content: m.content,
|
|
7495
7497
|
similarity: m.similarity
|
|
7496
7498
|
}));
|
|
7497
|
-
const { consolidateMemory: doConsolidate } = await import("./consolidate-
|
|
7499
|
+
const { consolidateMemory: doConsolidate } = await import("./consolidate-POPTUI54.mjs");
|
|
7498
7500
|
const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
|
|
7499
7501
|
if (decision.action === "create") return null;
|
|
7500
7502
|
if (decision.action === "supersede" && decision.content) {
|
|
@@ -8318,7 +8320,7 @@ function createLlmDecayClassifier(options) {
|
|
|
8318
8320
|
}
|
|
8319
8321
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
8320
8322
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
8321
|
-
const safe = redactor ? redactor.
|
|
8323
|
+
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
8322
8324
|
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input.updatedAt, now)}`;
|
|
8323
8325
|
let parsed;
|
|
8324
8326
|
try {
|
|
@@ -9415,17 +9417,22 @@ async function extractEntitiesForMemories(memories, options) {
|
|
|
9415
9417
|
const out = /* @__PURE__ */ new Map();
|
|
9416
9418
|
if (memories.length === 0) return out;
|
|
9417
9419
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
9418
|
-
const vocabulary =
|
|
9420
|
+
const vocabulary = [];
|
|
9421
|
+
for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
|
|
9422
|
+
vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
|
|
9423
|
+
}
|
|
9419
9424
|
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
9420
9425
|
When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
|
|
9421
9426
|
|
|
9422
9427
|
` : "";
|
|
9423
9428
|
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
9424
9429
|
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
9425
|
-
const
|
|
9430
|
+
const rows = [];
|
|
9431
|
+
for (const m of batch) {
|
|
9426
9432
|
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
|
|
9427
|
-
|
|
9428
|
-
}
|
|
9433
|
+
rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
|
|
9434
|
+
}
|
|
9435
|
+
const listing = rows.join("\n");
|
|
9429
9436
|
const parsed = await callPortalJsonCompletion({
|
|
9430
9437
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
9431
9438
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
|
@@ -14064,7 +14071,7 @@ __decorateClass([
|
|
|
14064
14071
|
], VaultFolder.prototype, "context", 2);
|
|
14065
14072
|
|
|
14066
14073
|
// src/lib/db/schema.ts
|
|
14067
|
-
var SDK_SCHEMA_VERSION =
|
|
14074
|
+
var SDK_SCHEMA_VERSION = 43;
|
|
14068
14075
|
var sdkSchema = appSchema2({
|
|
14069
14076
|
version: SDK_SCHEMA_VERSION,
|
|
14070
14077
|
tables: [
|
|
@@ -14112,7 +14119,16 @@ var sdkSchema = appSchema2({
|
|
|
14112
14119
|
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
14113
14120
|
{ name: "title", type: "string" },
|
|
14114
14121
|
{ name: "project_id", type: "string", isOptional: true, isIndexed: true },
|
|
14115
|
-
|
|
14122
|
+
// Indexed to match every other list-sorted created_at in this schema.
|
|
14123
|
+
// Note this single-column index is NOT what makes the conversation list
|
|
14124
|
+
// reads fast on native SQLite — they all filter is_deleted as well, and
|
|
14125
|
+
// SQLite will not combine the two indexes, so the composite created by
|
|
14126
|
+
// the v42 migration is what its planner actually uses. This declaration
|
|
14127
|
+
// still earns its keep on the other two adapters: LokiJS builds its
|
|
14128
|
+
// binary indices from `isIndexed` (and ignores `sql` migration steps
|
|
14129
|
+
// outright), and Postgres gathers statistics via autovacuum, so its
|
|
14130
|
+
// planner can use a single-column index the SQLite planner skips.
|
|
14131
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
14116
14132
|
{ name: "updated_at", type: "number" },
|
|
14117
14133
|
{ name: "is_deleted", type: "boolean", isIndexed: true },
|
|
14118
14134
|
{ name: "pinned_at", type: "number", isOptional: true }
|
|
@@ -14979,6 +14995,49 @@ var sdkMigrations = schemaMigrations2({
|
|
|
14979
14995
|
]
|
|
14980
14996
|
})
|
|
14981
14997
|
]
|
|
14998
|
+
},
|
|
14999
|
+
// v42 -> v43: Give the conversation list reads an index they can actually
|
|
15000
|
+
// sort on. All five of them (getConversationsOp, getConversationsLazyOp,
|
|
15001
|
+
// getConversationsByProjectOp, getConversationsByProjectLazyOp and the
|
|
15002
|
+
// keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
|
|
15003
|
+
// created_at desc`, so before this they resolved is_deleted through its
|
|
15004
|
+
// index and then built a temp B-tree over every live row just to return
|
|
15005
|
+
// the newest page.
|
|
15006
|
+
//
|
|
15007
|
+
// The index is COMPOSITE, and that is the whole point — a bare index on
|
|
15008
|
+
// created_at does not fix this. SQLite will not combine two single-column
|
|
15009
|
+
// indexes here, so with `is_deleted` indexed and `created_at` indexed
|
|
15010
|
+
// separately it still picks conversations_is_deleted and still temp-sorts.
|
|
15011
|
+
// It only prefers a lone created_at index once ANALYZE has populated
|
|
15012
|
+
// sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
|
|
15013
|
+
// native SQLite bindings, not this SDK. Every device is permanently in the
|
|
15014
|
+
// no-statistics state, so the index has to satisfy the filter and the sort
|
|
15015
|
+
// in one structure. `(is_deleted, created_at)` does: equality on the
|
|
15016
|
+
// leading column, ordered scan on the second, and the keyset boundary
|
|
15017
|
+
// becomes a range seek on the same index.
|
|
15018
|
+
//
|
|
15019
|
+
// unsafeExecuteSql, not addColumns: both columns already exist and
|
|
15020
|
+
// WatermelonDB has no add-index migration step, so raw SQL is the only way
|
|
15021
|
+
// to build one in place (same shape as the v19 -> v20 memory_vault
|
|
15022
|
+
// migration). IF NOT EXISTS keeps the step idempotent.
|
|
15023
|
+
//
|
|
15024
|
+
// Two limits worth knowing before someone re-measures this and finds it
|
|
15025
|
+
// missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
|
|
15026
|
+
// so existing browser databases gain nothing — web's only lever is the
|
|
15027
|
+
// `isIndexed` flag on the column itself. Second, this index reaches
|
|
15028
|
+
// MIGRATED databases only: WatermelonDB builds a fresh database purely from
|
|
15029
|
+
// the encoded schema, and its schema format cannot express a composite
|
|
15030
|
+
// index. The one hook that could (`unsafeSql` on the table) is a function,
|
|
15031
|
+
// and the LokiJS adapter posts the schema to its worker, so attaching it
|
|
15032
|
+
// makes the whole schema fail structuredClone and takes the database down
|
|
15033
|
+
// at setup for anyone on `useWebWorker: true`. Not worth it for an index.
|
|
15034
|
+
{
|
|
15035
|
+
toVersion: 43,
|
|
15036
|
+
steps: [
|
|
15037
|
+
unsafeExecuteSql(
|
|
15038
|
+
"CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
|
|
15039
|
+
)
|
|
15040
|
+
]
|
|
14982
15041
|
}
|
|
14983
15042
|
]
|
|
14984
15043
|
});
|
|
@@ -55,12 +55,14 @@ async function consolidateMemory(newContent, candidates, options) {
|
|
|
55
55
|
if (trimmed.length === 0) return fallback;
|
|
56
56
|
if (candidates.length === 0) return fallback;
|
|
57
57
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
58
|
-
const safeTrimmed = redactor ? redactor.
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
58
|
+
const safeTrimmed = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
59
|
+
const rows = [];
|
|
60
|
+
for (const [i, c] of candidates.entries()) {
|
|
61
|
+
const safeContent = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
62
|
+
rows.push(`[${i + 1}] (id: ${c.id}, sim: ${c.similarity.toFixed(2)})
|
|
63
|
+
${safeContent}`);
|
|
64
|
+
}
|
|
65
|
+
const candidateText = rows.join("\n");
|
|
64
66
|
const userMessage = `New memory:
|
|
65
67
|
${safeTrimmed}
|
|
66
68
|
|
package/dist/server/index.cjs
CHANGED
|
@@ -23580,12 +23580,14 @@ async function consolidateMemory(newContent, candidates, options) {
|
|
|
23580
23580
|
if (trimmed.length === 0) return fallback;
|
|
23581
23581
|
if (candidates.length === 0) return fallback;
|
|
23582
23582
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
23583
|
-
const safeTrimmed = redactor ? redactor.
|
|
23584
|
-
const
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
})
|
|
23583
|
+
const safeTrimmed = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
23584
|
+
const rows = [];
|
|
23585
|
+
for (const [i, c] of candidates.entries()) {
|
|
23586
|
+
const safeContent = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
23587
|
+
rows.push(`[${i + 1}] (id: ${c.id}, sim: ${c.similarity.toFixed(2)})
|
|
23588
|
+
${safeContent}`);
|
|
23589
|
+
}
|
|
23590
|
+
const candidateText = rows.join("\n");
|
|
23589
23591
|
const userMessage = `New memory:
|
|
23590
23592
|
${safeTrimmed}
|
|
23591
23593
|
|
|
@@ -24525,7 +24527,7 @@ __decorateClass([
|
|
|
24525
24527
|
], VaultFolder.prototype, "context", 2);
|
|
24526
24528
|
|
|
24527
24529
|
// src/lib/db/schema.ts
|
|
24528
|
-
var SDK_SCHEMA_VERSION =
|
|
24530
|
+
var SDK_SCHEMA_VERSION = 43;
|
|
24529
24531
|
var sdkSchema = (0, import_watermelondb12.appSchema)({
|
|
24530
24532
|
version: SDK_SCHEMA_VERSION,
|
|
24531
24533
|
tables: [
|
|
@@ -24573,7 +24575,16 @@ var sdkSchema = (0, import_watermelondb12.appSchema)({
|
|
|
24573
24575
|
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
24574
24576
|
{ name: "title", type: "string" },
|
|
24575
24577
|
{ name: "project_id", type: "string", isOptional: true, isIndexed: true },
|
|
24576
|
-
|
|
24578
|
+
// Indexed to match every other list-sorted created_at in this schema.
|
|
24579
|
+
// Note this single-column index is NOT what makes the conversation list
|
|
24580
|
+
// reads fast on native SQLite — they all filter is_deleted as well, and
|
|
24581
|
+
// SQLite will not combine the two indexes, so the composite created by
|
|
24582
|
+
// the v42 migration is what its planner actually uses. This declaration
|
|
24583
|
+
// still earns its keep on the other two adapters: LokiJS builds its
|
|
24584
|
+
// binary indices from `isIndexed` (and ignores `sql` migration steps
|
|
24585
|
+
// outright), and Postgres gathers statistics via autovacuum, so its
|
|
24586
|
+
// planner can use a single-column index the SQLite planner skips.
|
|
24587
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
24577
24588
|
{ name: "updated_at", type: "number" },
|
|
24578
24589
|
{ name: "is_deleted", type: "boolean", isIndexed: true },
|
|
24579
24590
|
{ name: "pinned_at", type: "number", isOptional: true }
|
|
@@ -25440,6 +25451,49 @@ var sdkMigrations = (0, import_migrations.schemaMigrations)({
|
|
|
25440
25451
|
]
|
|
25441
25452
|
})
|
|
25442
25453
|
]
|
|
25454
|
+
},
|
|
25455
|
+
// v42 -> v43: Give the conversation list reads an index they can actually
|
|
25456
|
+
// sort on. All five of them (getConversationsOp, getConversationsLazyOp,
|
|
25457
|
+
// getConversationsByProjectOp, getConversationsByProjectLazyOp and the
|
|
25458
|
+
// keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
|
|
25459
|
+
// created_at desc`, so before this they resolved is_deleted through its
|
|
25460
|
+
// index and then built a temp B-tree over every live row just to return
|
|
25461
|
+
// the newest page.
|
|
25462
|
+
//
|
|
25463
|
+
// The index is COMPOSITE, and that is the whole point — a bare index on
|
|
25464
|
+
// created_at does not fix this. SQLite will not combine two single-column
|
|
25465
|
+
// indexes here, so with `is_deleted` indexed and `created_at` indexed
|
|
25466
|
+
// separately it still picks conversations_is_deleted and still temp-sorts.
|
|
25467
|
+
// It only prefers a lone created_at index once ANALYZE has populated
|
|
25468
|
+
// sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
|
|
25469
|
+
// native SQLite bindings, not this SDK. Every device is permanently in the
|
|
25470
|
+
// no-statistics state, so the index has to satisfy the filter and the sort
|
|
25471
|
+
// in one structure. `(is_deleted, created_at)` does: equality on the
|
|
25472
|
+
// leading column, ordered scan on the second, and the keyset boundary
|
|
25473
|
+
// becomes a range seek on the same index.
|
|
25474
|
+
//
|
|
25475
|
+
// unsafeExecuteSql, not addColumns: both columns already exist and
|
|
25476
|
+
// WatermelonDB has no add-index migration step, so raw SQL is the only way
|
|
25477
|
+
// to build one in place (same shape as the v19 -> v20 memory_vault
|
|
25478
|
+
// migration). IF NOT EXISTS keeps the step idempotent.
|
|
25479
|
+
//
|
|
25480
|
+
// Two limits worth knowing before someone re-measures this and finds it
|
|
25481
|
+
// missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
|
|
25482
|
+
// so existing browser databases gain nothing — web's only lever is the
|
|
25483
|
+
// `isIndexed` flag on the column itself. Second, this index reaches
|
|
25484
|
+
// MIGRATED databases only: WatermelonDB builds a fresh database purely from
|
|
25485
|
+
// the encoded schema, and its schema format cannot express a composite
|
|
25486
|
+
// index. The one hook that could (`unsafeSql` on the table) is a function,
|
|
25487
|
+
// and the LokiJS adapter posts the schema to its worker, so attaching it
|
|
25488
|
+
// makes the whole schema fail structuredClone and takes the database down
|
|
25489
|
+
// at setup for anyone on `useWebWorker: true`. Not worth it for an index.
|
|
25490
|
+
{
|
|
25491
|
+
toVersion: 43,
|
|
25492
|
+
steps: [
|
|
25493
|
+
(0, import_migrations.unsafeExecuteSql)(
|
|
25494
|
+
"CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
|
|
25495
|
+
)
|
|
25496
|
+
]
|
|
25443
25497
|
}
|
|
25444
25498
|
]
|
|
25445
25499
|
});
|
|
@@ -28208,10 +28262,12 @@ async function classifyInjectionCandidates(candidates, options) {
|
|
|
28208
28262
|
);
|
|
28209
28263
|
}
|
|
28210
28264
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
28211
|
-
const
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
|
|
28265
|
+
const lines = [];
|
|
28266
|
+
for (const [i, c] of scope.entries()) {
|
|
28267
|
+
const safe = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
28268
|
+
lines.push(`[${i + 1}] ${safe}`);
|
|
28269
|
+
}
|
|
28270
|
+
const numbered = lines.join("\n");
|
|
28215
28271
|
let parsed;
|
|
28216
28272
|
try {
|
|
28217
28273
|
parsed = await callPortalJsonCompletion({
|
|
@@ -29533,7 +29589,7 @@ function createLlmDecayClassifier(options) {
|
|
|
29533
29589
|
}
|
|
29534
29590
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
29535
29591
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
29536
|
-
const safe = redactor ? redactor.
|
|
29592
|
+
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
29537
29593
|
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input.updatedAt, now)}`;
|
|
29538
29594
|
let parsed;
|
|
29539
29595
|
try {
|
|
@@ -30663,17 +30719,22 @@ async function extractEntitiesForMemories(memories, options) {
|
|
|
30663
30719
|
const out = /* @__PURE__ */ new Map();
|
|
30664
30720
|
if (memories.length === 0) return out;
|
|
30665
30721
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
30666
|
-
const vocabulary =
|
|
30722
|
+
const vocabulary = [];
|
|
30723
|
+
for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
|
|
30724
|
+
vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
|
|
30725
|
+
}
|
|
30667
30726
|
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
30668
30727
|
When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
|
|
30669
30728
|
|
|
30670
30729
|
` : "";
|
|
30671
30730
|
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
30672
30731
|
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
30673
|
-
const
|
|
30732
|
+
const rows = [];
|
|
30733
|
+
for (const m of batch) {
|
|
30674
30734
|
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
|
|
30675
|
-
|
|
30676
|
-
}
|
|
30735
|
+
rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
|
|
30736
|
+
}
|
|
30737
|
+
const listing = rows.join("\n");
|
|
30677
30738
|
const parsed = await callPortalJsonCompletion({
|
|
30678
30739
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
30679
30740
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
package/dist/server/index.d.mts
CHANGED
|
@@ -87,8 +87,12 @@ import { ResultCallback } from '@nozbe/watermelondb/utils/fp/Result';
|
|
|
87
87
|
* `topics_updated_at` is a SECOND timestamp because every topic writer pins
|
|
88
88
|
* `updated_at` on purpose (recall's recency multiplier) and both sync paths
|
|
89
89
|
* key on `updated_at`, so a topic-only change would neither upload nor merge
|
|
90
|
+
* - v43: Added a (is_deleted, created_at) index to conversations. Every
|
|
91
|
+
* conversation list read filters is_deleted and orders by created_at DESC,
|
|
92
|
+
* which previously meant a temp B-tree sort of the whole live set on every
|
|
93
|
+
* read. Structural only — no column added, no data rewritten
|
|
90
94
|
*/
|
|
91
|
-
declare const SDK_SCHEMA_VERSION =
|
|
95
|
+
declare const SDK_SCHEMA_VERSION = 43;
|
|
92
96
|
/**
|
|
93
97
|
* Combined WatermelonDB schema for all SDK storage modules.
|
|
94
98
|
*
|
|
@@ -175,6 +179,7 @@ declare const sdkSchema: Readonly<{
|
|
|
175
179
|
* - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
|
|
176
180
|
* - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
|
|
177
181
|
* - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
|
|
182
|
+
* - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
|
|
178
183
|
*/
|
|
179
184
|
declare const sdkMigrations: Readonly<{
|
|
180
185
|
validated: true;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -87,8 +87,12 @@ import { ResultCallback } from '@nozbe/watermelondb/utils/fp/Result';
|
|
|
87
87
|
* `topics_updated_at` is a SECOND timestamp because every topic writer pins
|
|
88
88
|
* `updated_at` on purpose (recall's recency multiplier) and both sync paths
|
|
89
89
|
* key on `updated_at`, so a topic-only change would neither upload nor merge
|
|
90
|
+
* - v43: Added a (is_deleted, created_at) index to conversations. Every
|
|
91
|
+
* conversation list read filters is_deleted and orders by created_at DESC,
|
|
92
|
+
* which previously meant a temp B-tree sort of the whole live set on every
|
|
93
|
+
* read. Structural only — no column added, no data rewritten
|
|
90
94
|
*/
|
|
91
|
-
declare const SDK_SCHEMA_VERSION =
|
|
95
|
+
declare const SDK_SCHEMA_VERSION = 43;
|
|
92
96
|
/**
|
|
93
97
|
* Combined WatermelonDB schema for all SDK storage modules.
|
|
94
98
|
*
|
|
@@ -175,6 +179,7 @@ declare const sdkSchema: Readonly<{
|
|
|
175
179
|
* - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
|
|
176
180
|
* - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
|
|
177
181
|
* - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
|
|
182
|
+
* - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
|
|
178
183
|
*/
|
|
179
184
|
declare const sdkMigrations: Readonly<{
|
|
180
185
|
validated: true;
|
package/dist/server/index.mjs
CHANGED
|
@@ -1319,7 +1319,7 @@ __decorateClass([
|
|
|
1319
1319
|
], VaultFolder.prototype, "context", 2);
|
|
1320
1320
|
|
|
1321
1321
|
// src/lib/db/schema.ts
|
|
1322
|
-
var SDK_SCHEMA_VERSION =
|
|
1322
|
+
var SDK_SCHEMA_VERSION = 43;
|
|
1323
1323
|
var sdkSchema = (0, import_watermelondb12.appSchema)({
|
|
1324
1324
|
version: SDK_SCHEMA_VERSION,
|
|
1325
1325
|
tables: [
|
|
@@ -1367,7 +1367,16 @@ var sdkSchema = (0, import_watermelondb12.appSchema)({
|
|
|
1367
1367
|
{ name: "conversation_id", type: "string", isIndexed: true },
|
|
1368
1368
|
{ name: "title", type: "string" },
|
|
1369
1369
|
{ name: "project_id", type: "string", isOptional: true, isIndexed: true },
|
|
1370
|
-
|
|
1370
|
+
// Indexed to match every other list-sorted created_at in this schema.
|
|
1371
|
+
// Note this single-column index is NOT what makes the conversation list
|
|
1372
|
+
// reads fast on native SQLite — they all filter is_deleted as well, and
|
|
1373
|
+
// SQLite will not combine the two indexes, so the composite created by
|
|
1374
|
+
// the v42 migration is what its planner actually uses. This declaration
|
|
1375
|
+
// still earns its keep on the other two adapters: LokiJS builds its
|
|
1376
|
+
// binary indices from `isIndexed` (and ignores `sql` migration steps
|
|
1377
|
+
// outright), and Postgres gathers statistics via autovacuum, so its
|
|
1378
|
+
// planner can use a single-column index the SQLite planner skips.
|
|
1379
|
+
{ name: "created_at", type: "number", isIndexed: true },
|
|
1371
1380
|
{ name: "updated_at", type: "number" },
|
|
1372
1381
|
{ name: "is_deleted", type: "boolean", isIndexed: true },
|
|
1373
1382
|
{ name: "pinned_at", type: "number", isOptional: true }
|
|
@@ -2234,6 +2243,49 @@ var sdkMigrations = (0, import_migrations.schemaMigrations)({
|
|
|
2234
2243
|
]
|
|
2235
2244
|
})
|
|
2236
2245
|
]
|
|
2246
|
+
},
|
|
2247
|
+
// v42 -> v43: Give the conversation list reads an index they can actually
|
|
2248
|
+
// sort on. All five of them (getConversationsOp, getConversationsLazyOp,
|
|
2249
|
+
// getConversationsByProjectOp, getConversationsByProjectLazyOp and the
|
|
2250
|
+
// keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
|
|
2251
|
+
// created_at desc`, so before this they resolved is_deleted through its
|
|
2252
|
+
// index and then built a temp B-tree over every live row just to return
|
|
2253
|
+
// the newest page.
|
|
2254
|
+
//
|
|
2255
|
+
// The index is COMPOSITE, and that is the whole point — a bare index on
|
|
2256
|
+
// created_at does not fix this. SQLite will not combine two single-column
|
|
2257
|
+
// indexes here, so with `is_deleted` indexed and `created_at` indexed
|
|
2258
|
+
// separately it still picks conversations_is_deleted and still temp-sorts.
|
|
2259
|
+
// It only prefers a lone created_at index once ANALYZE has populated
|
|
2260
|
+
// sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
|
|
2261
|
+
// native SQLite bindings, not this SDK. Every device is permanently in the
|
|
2262
|
+
// no-statistics state, so the index has to satisfy the filter and the sort
|
|
2263
|
+
// in one structure. `(is_deleted, created_at)` does: equality on the
|
|
2264
|
+
// leading column, ordered scan on the second, and the keyset boundary
|
|
2265
|
+
// becomes a range seek on the same index.
|
|
2266
|
+
//
|
|
2267
|
+
// unsafeExecuteSql, not addColumns: both columns already exist and
|
|
2268
|
+
// WatermelonDB has no add-index migration step, so raw SQL is the only way
|
|
2269
|
+
// to build one in place (same shape as the v19 -> v20 memory_vault
|
|
2270
|
+
// migration). IF NOT EXISTS keeps the step idempotent.
|
|
2271
|
+
//
|
|
2272
|
+
// Two limits worth knowing before someone re-measures this and finds it
|
|
2273
|
+
// missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
|
|
2274
|
+
// so existing browser databases gain nothing — web's only lever is the
|
|
2275
|
+
// `isIndexed` flag on the column itself. Second, this index reaches
|
|
2276
|
+
// MIGRATED databases only: WatermelonDB builds a fresh database purely from
|
|
2277
|
+
// the encoded schema, and its schema format cannot express a composite
|
|
2278
|
+
// index. The one hook that could (`unsafeSql` on the table) is a function,
|
|
2279
|
+
// and the LokiJS adapter posts the schema to its worker, so attaching it
|
|
2280
|
+
// makes the whole schema fail structuredClone and takes the database down
|
|
2281
|
+
// at setup for anyone on `useWebWorker: true`. Not worth it for an index.
|
|
2282
|
+
{
|
|
2283
|
+
toVersion: 43,
|
|
2284
|
+
steps: [
|
|
2285
|
+
(0, import_migrations.unsafeExecuteSql)(
|
|
2286
|
+
"CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
|
|
2287
|
+
)
|
|
2288
|
+
]
|
|
2237
2289
|
}
|
|
2238
2290
|
]
|
|
2239
2291
|
});
|
|
@@ -4965,10 +5017,12 @@ async function classifyInjectionCandidates(candidates, options) {
|
|
|
4965
5017
|
);
|
|
4966
5018
|
}
|
|
4967
5019
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
4968
|
-
const
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
5020
|
+
const lines = [];
|
|
5021
|
+
for (const [i, c] of scope.entries()) {
|
|
5022
|
+
const safe = redactor ? (await redactor.redactTextAsync(c.content)).text : c.content;
|
|
5023
|
+
lines.push(`[${i + 1}] ${safe}`);
|
|
5024
|
+
}
|
|
5025
|
+
const numbered = lines.join("\n");
|
|
4972
5026
|
let parsed;
|
|
4973
5027
|
try {
|
|
4974
5028
|
parsed = await callPortalJsonCompletion({
|
|
@@ -5449,7 +5503,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
5449
5503
|
content: m.content,
|
|
5450
5504
|
similarity: m.similarity
|
|
5451
5505
|
}));
|
|
5452
|
-
const { consolidateMemory: doConsolidate } = await import("./consolidate-
|
|
5506
|
+
const { consolidateMemory: doConsolidate } = await import("./consolidate-LMIDJFR6.mjs");
|
|
5453
5507
|
const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
|
|
5454
5508
|
if (decision.action === "create") return null;
|
|
5455
5509
|
if (decision.action === "supersede" && decision.content) {
|
|
@@ -6273,7 +6327,7 @@ function createLlmDecayClassifier(options) {
|
|
|
6273
6327
|
}
|
|
6274
6328
|
if (!content || content.trim().length === 0) return ruleVerdict;
|
|
6275
6329
|
const trimmed = content.trim().slice(0, MAX_CONTENT_CHARS);
|
|
6276
|
-
const safe = redactor ? redactor.
|
|
6330
|
+
const safe = redactor ? (await redactor.redactTextAsync(trimmed)).text : trimmed;
|
|
6277
6331
|
const meta = `factType: ${input.factType ?? "none"}; ageDays: ${ageDays(input.updatedAt, now)}`;
|
|
6278
6332
|
let parsed;
|
|
6279
6333
|
try {
|
|
@@ -7370,17 +7424,22 @@ async function extractEntitiesForMemories(memories, options) {
|
|
|
7370
7424
|
const out = /* @__PURE__ */ new Map();
|
|
7371
7425
|
if (memories.length === 0) return out;
|
|
7372
7426
|
const redactor = resolvePiiRedactor(options.piiRedaction);
|
|
7373
|
-
const vocabulary =
|
|
7427
|
+
const vocabulary = [];
|
|
7428
|
+
for (const name of (options.existingEntityNames ?? []).filter((n) => n.trim().length > 0).slice(0, MAX_VOCABULARY_NAMES)) {
|
|
7429
|
+
vocabulary.push(redactor ? (await redactor.redactTextAsync(name)).text : name);
|
|
7430
|
+
}
|
|
7374
7431
|
const vocabularyNote = vocabulary.length > 0 ? `The user's existing topics: ${vocabulary.join(", ")}.
|
|
7375
7432
|
When a memory refers to one of these, reuse the EXACT existing name (same spelling, casing, spacing and punctuation) instead of a variant \u2014 if "Foo.ai" is listed, never emit "Foo ai" or "Foo AI", and if "Jane Roe" is listed, emit that rather than a bare "Jane".
|
|
7376
7433
|
|
|
7377
7434
|
` : "";
|
|
7378
7435
|
for (let i = 0; i < memories.length; i += TOPIC_EXTRACTION_BATCH_SIZE) {
|
|
7379
7436
|
const batch = memories.slice(i, i + TOPIC_EXTRACTION_BATCH_SIZE);
|
|
7380
|
-
const
|
|
7437
|
+
const rows = [];
|
|
7438
|
+
for (const m of batch) {
|
|
7381
7439
|
const content = m.content.slice(0, MAX_CHARS_PER_MEMORY).replace(/\s+/g, " ").trim();
|
|
7382
|
-
|
|
7383
|
-
}
|
|
7440
|
+
rows.push(`${m.id}: ${redactor ? (await redactor.redactTextAsync(content)).text : content}`);
|
|
7441
|
+
}
|
|
7442
|
+
const listing = rows.join("\n");
|
|
7384
7443
|
const parsed = await callPortalJsonCompletion({
|
|
7385
7444
|
...options.apiKey !== void 0 && { apiKey: options.apiKey },
|
|
7386
7445
|
...options.getToken !== void 0 && { getToken: options.getToken },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anuma/sdk",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -136,8 +136,9 @@
|
|
|
136
136
|
"lint:fix": "eslint . --fix",
|
|
137
137
|
"format": "prettier --write .",
|
|
138
138
|
"format:check": "prettier --check .",
|
|
139
|
-
"typecheck": "tsc --noEmit && pnpm run typecheck:test",
|
|
139
|
+
"typecheck": "tsc --noEmit && pnpm run typecheck:test && pnpm run check:excluded-suites",
|
|
140
140
|
"typecheck:test": "tsc -p tsconfig.test.json",
|
|
141
|
+
"check:excluded-suites": "node scripts/assert-excluded-suites-still-fail.mjs",
|
|
141
142
|
"knip": "knip",
|
|
142
143
|
"check:export-consumers": "node scripts/assert-export-consumers.mjs",
|
|
143
144
|
"check-exports": "publint && attw --pack --ignore-rules no-resolution false-cjs && node scripts/assert-no-client-self-import.mjs",
|