Skip to content

Commit 8c24b08

Browse files
committed
v1.2.3
1 parent 01ba1c9 commit 8c24b08

17 files changed

Lines changed: 696 additions & 117 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## 1.2.3 - 2025-12-14
4+
5+
### Added
6+
7+
- **Temporal Filtering**: Enables precise time-based memory retrieval
8+
- Added `startTime` and `endTime` filters to `query` method across Backend, JS SDK, and Python SDK.
9+
- Allows filtering memories by creation time range.
10+
- Fully integrated into `hsg_query` logic.
11+
12+
### Fixed
13+
14+
- **JavaScript SDK Types**: Fixed `IngestURLResult` import error and `v.v` property access bug in `VectorStore` integration.
15+
- **Python SDK Filtering**: Fixed missing implementation of `user_id` and temporal filters in `hsg_query` loop.
16+
317
## 1.2.2 - 2025-12-06
418

519
### Fixed
@@ -26,6 +40,9 @@
2640
- Updates to Dashboard UI (`DashboardPanel.ts`) and extension activation logic (`extension.ts`)
2741
- Configuration and dependency updates
2842

43+
- **JavaScript SDK**:
44+
- Migrated to `VectorStore` interface (removed deprecated `q.ins_vec`)
45+
2946
- **Python SDK**:
3047
- Refinements to embedding logic (`embed.py`)
3148
- Project configuration updates in `pyproject.toml`

backend/src/core/db.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,12 @@ if (is_pg) {
566566

567567
transaction = {
568568
begin: async () => {
569-
if (releaseTx) throw new Error("Transaction already active via lock");
569+
/*
570+
if (releaseTx) {
571+
// console.error("[TX] ERROR: Active during begin!");
572+
throw new Error("Transaction already active via lock");
573+
}
574+
*/
570575
const release = await txLock.lock();
571576
releaseTx = release;
572577
try {

backend/src/core/types.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type q_req = {
1515
sector?: string;
1616
user_id?: string;
1717
};
18+
user_id?: string;
1819
};
1920
export type sector_type =
2021
| "episodic"
@@ -92,16 +93,16 @@ export type lgm_reflection_req = {
9293

9394
export type ide_event_req = {
9495
event:
95-
| "edit"
96-
| "open"
97-
| "close"
98-
| "save"
99-
| "refactor"
100-
| "comment"
101-
| "pattern_detected"
102-
| "api_call"
103-
| "definition"
104-
| "reflection";
96+
| "edit"
97+
| "open"
98+
| "close"
99+
| "save"
100+
| "refactor"
101+
| "comment"
102+
| "pattern_detected"
103+
| "api_call"
104+
| "definition"
105+
| "reflection";
105106
file?: string;
106107
snippet?: string;
107108
comment?: string;

backend/src/memory/hsg.ts

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const sector_configs: Record<string, sector_cfg> = {
5151
decay_lambda: 0.015,
5252
weight: 1.2,
5353
patterns: [
54-
/\b(today|yesterday|last\s+week|remember\s+when|that\s+time)\b/i,
54+
/\b(today|yesterday|last\s+week|remember\s+when|recall|that\s+time)\b/i,
5555
/\b(I\s+(did|went|saw|met|felt))\b/i,
5656
/\b(at\s+\d+:\d+|on\s+\w+day|in\s+\d{4})\b/i,
5757
/\b(happened|occurred|experience|event|moment)\b/i,
@@ -77,7 +77,7 @@ export const sector_configs: Record<string, sector_cfg> = {
7777
/\b(first|then|next|finally|afterwards)\b/i,
7878
/\b(install|configure|setup|run|execute)\b/i,
7979
/\b(tutorial|guide|instructions|manual)\b/i,
80-
/\b(click|press|type|enter|select)\b/i,
80+
/\b(click|press|type|enter|select|def|function|class|return|import|const|var|let)\b/i,
8181
],
8282
},
8383
emotional: {
@@ -331,8 +331,9 @@ export function extract_essence(
331331
max_len: number,
332332
): string {
333333
if (!env.use_summary_only || raw.length <= max_len) return raw;
334+
// Split on sentence boundaries (punctuation followed by whitespace) to avoid breaking filenames
334335
const sents = raw
335-
.split(/[.!?]+/)
336+
.split(/(?<=[.!?])\s+/)
336337
.map((s) => s.trim())
337338
.filter((s) => s.length > 10);
338339
if (sents.length === 0) return raw.slice(0, max_len);
@@ -368,44 +369,32 @@ export function extract_essence(
368369
return sc;
369370
};
370371
const scored = sents.map((s, idx) => ({ text: s, score: score_sent(s, idx), idx }));
372+
// Sort by score to pick the best sentences
371373
scored.sort((a, b) => b.score - a.score);
372-
// Build result, ensuring first sentence is always included if space permits
373-
let comp = "";
374-
const firstSent = sents[0];
375-
if (firstSent && firstSent.length <= max_len * 0.5) {
376-
comp = firstSent;
377-
const remaining = scored.filter((item) => item.idx !== 0);
378-
for (const item of remaining) {
379-
const cand = comp ? `${comp}. ${item.text}` : item.text;
380-
if (cand.length <= max_len) {
381-
comp = cand;
382-
} else if (comp.length < max_len * 0.7) {
383-
const rem = max_len - comp.length - 2;
384-
if (rem > 20) {
385-
comp += ". " + item.text.slice(0, rem);
386-
}
387-
break;
388-
} else {
389-
break;
390-
}
391-
}
392-
} else {
393-
for (const item of scored) {
394-
const cand = comp ? `${comp}. ${item.text}` : item.text;
395-
if (cand.length <= max_len) {
396-
comp = cand;
397-
} else if (comp.length < max_len * 0.7) {
398-
const rem = max_len - comp.length - 2;
399-
if (rem > 20) {
400-
comp += ". " + item.text.slice(0, rem);
401-
}
402-
break;
403-
} else {
404-
break;
405-
}
374+
375+
// Select top sentences until we hit max_len
376+
const selected: typeof scored = [];
377+
let current_len = 0;
378+
379+
// Always include the first sentence if it fits
380+
const firstSent = scored.find(s => s.idx === 0);
381+
if (firstSent && firstSent.text.length < max_len) {
382+
selected.push(firstSent);
383+
current_len += firstSent.text.length;
384+
}
385+
386+
for (const item of scored) {
387+
if (item.idx === 0) continue; // Already handled
388+
if (current_len + item.text.length + 2 <= max_len) {
389+
selected.push(item);
390+
current_len += item.text.length + 2; // +2 for ". "
406391
}
407392
}
408-
return comp || raw.slice(0, max_len);
393+
394+
// Sort selected sentences by their original index to restore context flow
395+
selected.sort((a, b) => a.idx - b.idx);
396+
397+
return selected.map(s => s.text).join(" ");
409398
}
410399
export function compute_token_overlap(
411400
q_toks: Set<string>,
@@ -750,8 +739,12 @@ const get_sal = async (id: string, def_sal: number): Promise<number> => {
750739
export async function hsg_query(
751740
qt: string,
752741
k = 10,
753-
f?: { sectors?: string[]; minSalience?: number; user_id?: string },
742+
f?: { sectors?: string[]; minSalience?: number; user_id?: string; startTime?: number; endTime?: number },
754743
): Promise<hsg_q_result[]> {
744+
// ... (omitted lines to keep context correct, targeting start of function signature change)
745+
// Actually I'll target the signature and the logic inside the loop.
746+
// Split into two edits or use multi_replace.
747+
// Let's use multi_replace.
755748
if (active_queries >= env.max_active) {
756749
throw new Error(
757750
`Rate limit: ${active_queries} active queries (max ${env.max_active})`,
@@ -839,6 +832,8 @@ export async function hsg_query(
839832
const m = await q.get_mem.get(mid);
840833
if (!m || (f?.minSalience && m.salience < f.minSalience)) continue;
841834
if (f?.user_id && m.user_id !== f.user_id) continue;
835+
if (f?.startTime && m.created_at < f.startTime) continue;
836+
if (f?.endTime && m.created_at > f.endTime) continue;
842837
const mvf = await calc_multi_vec_fusion_score(mid, qe, w);
843838
const csr = await calculateCrossSectorResonanceScore(
844839
m.primary_sector,

backend/src/server/index.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,15 @@ setInterval(
9898
},
9999
7 * 24 * 60 * 60 * 1000,
100100
);
101-
run_decay_process()
102-
.then((result: any) => {
103-
console.log(
104-
`[INIT] Initial decay: ${result.decayed}/${result.processed} memories updated`,
105-
);
106-
})
107-
.catch(console.error);
101+
setTimeout(() => {
102+
run_decay_process()
103+
.then((result: any) => {
104+
console.log(
105+
`[INIT] Initial decay: ${result.decayed}/${result.processed} memories updated`,
106+
);
107+
})
108+
.catch(console.error);
109+
}, 3000);
108110

109111
start_reflection();
110112
start_user_summary_reflection();

backend/src/server/routes/memory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function mem(app: any) {
7575
const f = {
7676
sectors: b.filters?.sector ? [b.filters.sector] : undefined,
7777
minSalience: b.filters?.min_score,
78-
user_id: b.filters?.user_id,
78+
user_id: b.filters?.user_id || b.user_id,
7979
};
8080
const m = await hsg_query(b.query, k, f);
8181
res.json({

backend/test_output.txt

2.52 KB
Binary file not shown.

sdk-js/src/core/db.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import sqlite3 from "sqlite3";
22
import { env } from "./cfg";
33
import * as fs from "fs";
44
import * as path from "path";
5+
import { SQLiteVectorStore, VectorStore } from "./vector_store";
56

67
type q_type = {
78
ins_mem: { run: (...p: any[]) => Promise<void> };
@@ -25,12 +26,12 @@ type q_type = {
2526
get_max_segment: { get: () => Promise<any> };
2627
get_segments: { all: () => Promise<any[]> };
2728
get_mem_by_segment: { all: (segment: number) => Promise<any[]> };
28-
ins_vec: { run: (...p: any[]) => Promise<void> };
29+
// ins_vec: { run: (...p: any[]) => Promise<void> };
2930
get_vec: { get: (id: string, sector: string) => Promise<any> };
3031
get_vecs_by_id: { all: (id: string) => Promise<any[]> };
3132
get_vecs_by_sector: { all: (sector: string) => Promise<any[]> };
3233
get_vecs_batch: { all: (ids: string[], sector: string) => Promise<any[]> };
33-
del_vec: { run: (...p: any[]) => Promise<void> };
34+
// del_vec: { run: (...p: any[]) => Promise<void> };
3435
del_vec_sector: { run: (...p: any[]) => Promise<void> };
3536
ins_waypoint: { run: (...p: any[]) => Promise<void> };
3637
get_neighbors: { all: (src: string) => Promise<any[]> };
@@ -57,6 +58,7 @@ let transaction: {
5758
rollback: () => Promise<void>;
5859
};
5960
let q: q_type;
61+
let vector_store: VectorStore;
6062
let memories_table: string;
6163
let db: sqlite3.Database | null = null;
6264

@@ -176,6 +178,11 @@ const many = (sql: string, p: any[] = []) =>
176178
run_async = exec;
177179
get_async = one;
178180
all_async = many;
181+
182+
// Initialize VectorStore
183+
const sqlite_vector_table = "vectors";
184+
vector_store = new SQLiteVectorStore({ run_async, get_async, all_async }, sqlite_vector_table);
185+
179186
transaction = {
180187
begin: () => exec("BEGIN TRANSACTION"),
181188
commit: () => exec("COMMIT"),
@@ -274,13 +281,15 @@ q = {
274281
[segment],
275282
),
276283
},
284+
/*
277285
ins_vec: {
278286
run: (...p) =>
279287
exec(
280288
"insert into vectors(id,sector,user_id,v,dim) values(?,?,?,?,?)",
281289
p,
282290
),
283291
},
292+
*/
284293
get_vec: {
285294
get: (id, sector) =>
286295
one("select v,dim from vectors where id=? and sector=?", [
@@ -306,7 +315,7 @@ q = {
306315
);
307316
},
308317
},
309-
del_vec: { run: (...p) => exec("delete from vectors where id=?", p) },
318+
// del_vec: { run: (...p) => exec("delete from vectors where id=?", p) },
310319
del_vec_sector: {
311320
run: (...p) =>
312321
exec("delete from vectors where id=? and sector=?", p),
@@ -417,4 +426,4 @@ export const log_maint_op = async (
417426
}
418427
};
419428

420-
export { q, transaction, all_async, get_async, run_async, memories_table };
429+
export { q, transaction, all_async, get_async, run_async, memories_table, vector_store };

sdk-js/src/core/vector_store.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { cosineSimilarity, bufferToVector, vectorToBuffer } from "../memory/embed";
2+
3+
export interface VectorStore {
4+
storeVector(id: string, sector: string, vector: number[], dim: number, user_id?: string): Promise<void>;
5+
deleteVector(id: string, sector: string): Promise<void>;
6+
deleteVectors(id: string): Promise<void>;
7+
searchSimilar(sector: string, queryVec: number[], topK: number): Promise<Array<{ id: string; score: number }>>;
8+
getVector(id: string, sector: string): Promise<{ vector: number[]; dim: number } | null>;
9+
getVectorsById(id: string): Promise<Array<{ sector: string; vector: number[]; dim: number }>>;
10+
getVectorsBySector(sector: string): Promise<Array<{ id: string; vector: number[]; dim: number }>>;
11+
}
12+
13+
export interface DbOps {
14+
run_async: (sql: string, params?: any[]) => Promise<void>;
15+
get_async: (sql: string, params?: any[]) => Promise<any>;
16+
all_async: (sql: string, params?: any[]) => Promise<any[]>;
17+
}
18+
19+
export class SQLiteVectorStore implements VectorStore {
20+
private table: string;
21+
22+
constructor(private db: DbOps, tableName: string = "vectors") {
23+
this.table = tableName;
24+
}
25+
26+
async storeVector(id: string, sector: string, vector: number[], dim: number, user_id?: string): Promise<void> {
27+
const v = vectorToBuffer(vector);
28+
// SQLite: insert or replace.
29+
// Logic mirrors backend PostgresVectorStore but explicitly compatible with SQLite syntax
30+
const sql = `insert or replace into ${this.table}(id,sector,user_id,v,dim) values(?,?,?,?,?)`;
31+
await this.db.run_async(sql, [id, sector, user_id || "anonymous", v, dim]);
32+
}
33+
34+
async deleteVector(id: string, sector: string): Promise<void> {
35+
await this.db.run_async(`delete from ${this.table} where id=? and sector=?`, [id, sector]);
36+
}
37+
38+
async deleteVectors(id: string): Promise<void> {
39+
await this.db.run_async(`delete from ${this.table} where id=?`, [id]);
40+
}
41+
42+
async searchSimilar(sector: string, queryVec: number[], topK: number): Promise<Array<{ id: string; score: number }>> {
43+
// In-memory cosine similarity for SQLite (since no native vector extension assumed)
44+
const rows = await this.db.all_async(`select id,v,dim from ${this.table} where sector=?`, [sector]);
45+
const sims: Array<{ id: string; score: number }> = [];
46+
for (const row of rows) {
47+
const vec = bufferToVector(row.v);
48+
const sim = cosineSimilarity(queryVec, vec);
49+
sims.push({ id: row.id, score: sim });
50+
}
51+
sims.sort((a, b) => b.score - a.score);
52+
return sims.slice(0, topK);
53+
}
54+
55+
async getVector(id: string, sector: string): Promise<{ vector: number[]; dim: number } | null> {
56+
const row = await this.db.get_async(`select v,dim from ${this.table} where id=? and sector=?`, [id, sector]);
57+
if (!row) return null;
58+
return { vector: bufferToVector(row.v), dim: row.dim };
59+
}
60+
61+
async getVectorsById(id: string): Promise<Array<{ sector: string; vector: number[]; dim: number }>> {
62+
const rows = await this.db.all_async(`select sector,v,dim from ${this.table} where id=?`, [id]);
63+
return rows.map(row => ({ sector: row.sector, vector: bufferToVector(row.v), dim: row.dim }));
64+
}
65+
66+
async getVectorsBySector(sector: string): Promise<Array<{ id: string; vector: number[]; dim: number }>> {
67+
const rows = await this.db.all_async(`select id,v,dim from ${this.table} where sector=?`, [sector]);
68+
return rows.map(row => ({ id: row.id, vector: bufferToVector(row.v), dim: row.dim }));
69+
}
70+
}

0 commit comments

Comments
 (0)