Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
6325889
Bug fix: halt chat generation when view not active
fiorittoev Jun 11, 2026
047e7e1
Bug fix: Non existing node connections show the same as existing conn…
fiorittoev Jun 11, 2026
de6c3b2
Correction Signal Detector (Backend
fiorittoev Jun 12, 2026
70f9727
Correction-Triggered Extraction
fiorittoev Jun 13, 2026
2781cba
Commit 3 — Pending Changeset Amendment Engine (Backend)
fiorittoev Jun 13, 2026
3fc84e7
Manual "Extract Now" Trigger
fiorittoev Jun 13, 2026
8511e2d
Diff Panel Amendment Badge & Integration Tests
fiorittoev Jun 14, 2026
4c86912
Ui changes
fiorittoev Jun 14, 2026
8e3781e
implement correction signal detection for memory agent and add corres…
AashishH15 Jun 15, 2026
97ce7ba
implement core IPC commands and utilities including changeset seeding…
AashishH15 Jun 15, 2026
7db96b6
implement amendment module to update or create pending memory changes…
AashishH15 Jun 15, 2026
15cef3a
Fixed Broken Loop & Redundant Check
AashishH15 Jun 15, 2026
234f16c
feat: implement amendment module to support updating existing pending…
AashishH15 Jun 15, 2026
e4de6f7
Fixed Off-By-One Message Query
AashishH15 Jun 15, 2026
e9de02e
implement ChatPanel component with message rendering, editing, and st…
AashishH15 Jun 15, 2026
c6472ef
Promise Cache inside getNodes()
AashishH15 Jun 15, 2026
01deee0
Database Query Session Filtering
AashishH15 Jun 15, 2026
529db4b
clear cache
AashishH15 Jun 15, 2026
c88e5af
removed path fiorittoev/Desktop/projects/MindVault/core/src/memory_ag…
AashishH15 Jun 15, 2026
59d0319
Simplified Function Signature: Refactored the return type of find_pen…
AashishH15 Jun 15, 2026
67e277b
UTF-8 Byte Length Aware Advancement
AashishH15 Jun 15, 2026
377ef3d
Punctuation Trimming: Updated the direct negation scan loop inside co…
AashishH15 Jun 15, 2026
6742e29
Updated the query in
AashishH15 Jun 15, 2026
e150852
Removed Duplicate Helper: Deleted the private jaccard_similarity help…
AashishH15 Jun 15, 2026
2104524
Updated getNodes(isRedactedUnlocked?: boolean) to map the requested …
AashishH15 Jun 15, 2026
25ac424
Modified WikiLinkBadge to look up nodes inside ExistingNodesContext i…
AashishH15 Jun 15, 2026
184cf27
updated the database query to retrieve previous_message by adding the…
AashishH15 Jun 15, 2026
4eeefa8
updated the SELECT SQL query to filter by AND status = 'pending'
AashishH15 Jun 15, 2026
6e9aeba
Replaced chat_history[i] direct indexing with a safe bounds check usi…
AashishH15 Jun 15, 2026
425cc0c
Passed the isRedactedUnlocked prop to getAllNodes() (so it executes a…
AashishH15 Jun 15, 2026
00adfb0
STOPWORDS as a public slice inside similarity.rs. In correction.rs's …
AashishH15 Jun 15, 2026
43ea69a
The loop over candidates in amend_or_create_changeset tracks a HashSe…
AashishH15 Jun 15, 2026
ef7ccfd
Moved parseJSON to the module level (defined outside the React compon…
AashishH15 Jun 15, 2026
1afd12f
Replaced .contains(...) with contains_phrase_with_boundaries(&message…
AashishH15 Jun 15, 2026
3f7d354
Replaced Native alert() with a Custom Modal Dialog
AashishH15 Jun 15, 2026
3f70181
Modified the query in lib.rs to join with the changesets table and fi…
AashishH15 Jun 15, 2026
6936a53
Modified the isBroken computation inside the WikiLinkBadge component …
AashishH15 Jun 15, 2026
f538eff
implement expanded node editor with Markdown preview, tag management,…
AashishH15 Jun 15, 2026
7e73e22
id is crypto.randomUUID() (a random UUID v4), so sorting by it produc…
AashishH15 Jun 15, 2026
1aee0b1
changed ORDER BY created_at ASC, id ASC → ORDER BY created_at ASC, ro…
AashishH15 Jun 15, 2026
5d39f62
Added let mut is_correction = false; to track correction state (line …
AashishH15 Jun 15, 2026
e653d52
Line 27: Added clearNodesCache to the import from ../services/nodes
AashishH15 Jun 15, 2026
a7e146e
UPDATE path now includes item_type (derived from candidate.action) in…
AashishH15 Jun 15, 2026
c08f29c
Signature: fn(Value, ...) -> Result<Value, String> → fn(&mut Value, ...)
AashishH15 Jun 15, 2026
4bd3a76
Replaced the 58-line manual calendar decomposition (chrono_now_iso + …
AashishH15 Jun 15, 2026
35e5a1b
removed the no-op sort_order = sort_order self-assignment from the UP…
AashishH15 Jun 15, 2026
80327f2
implement DiffPanel for reviewing and committing memory changesets
AashishH15 Jun 15, 2026
9ddb6f8
refactored the CorrectionSignal detection in the memory agent.
AashishH15 Jun 15, 2026
c825708
Added chrono = { version = "0.4", features = ["serde"] } to Cargo.toml
AashishH15 Jun 15, 2026
e227b3a
Added Prop: Extended ChatPanelProps in
AashishH15 Jun 15, 2026
b4d59dd
Defined labelText: Added a React.useMemo function at the top of WikiL…
AashishH15 Jun 15, 2026
c0dd922
Refactored the negation scan
AashishH15 Jun 15, 2026
b2cb4ea
implement core lib module with database debugging, file export, and r…
AashishH15 Jun 15, 2026
afd065f
Removed clearNodesCache from the import list
AashishH15 Jun 15, 2026
1aacfa3
Changed the default context value in
AashishH15 Jun 15, 2026
d13a1af
Reordered DB Queries: Modified
AashishH15 Jun 15, 2026
9f66818
Started Transaction Early: In amendment.rs:122-160, we now start the …
AashishH15 Jun 15, 2026
64d7576
Fixed the Debounce Bypass Bug: Updated lib.rs:683-698 to always retri…
AashishH15 Jun 15, 2026
2e4a881
Fixed Context Provider in NodeEditorDetail.tsx: Updated NodeEditorDet…
AashishH15 Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2.11", default-features = false, features = ["wry"] }
tauri = { version = "2.11", default-features = false, features = ["wry", "test"] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand All @@ -34,3 +34,5 @@ async-trait = "0.1"
tiktoken-rs = "0.5"
rfd = "0.15"
governor = "0.6"
chrono = { version = "0.4", features = ["serde"] }

2 changes: 1 addition & 1 deletion core/src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub fn get_chat_history(db: &Connection) -> Result<Vec<ChatMessage>, crate::AppE
"SELECT id, role, content, created_at
FROM session_messages
WHERE session_id = ?1
ORDER BY created_at ASC, id ASC;",
ORDER BY created_at ASC, rowid ASC;",
)
.map_err(|err| {
eprintln!("Database error preparing chat history query: {err}");
Expand Down
131 changes: 122 additions & 9 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};

use chat::ChatMessage;
use rusqlite::OptionalExtension;
use rusqlite::{params, Connection, Row};
use serde::Serialize;
use tauri::Manager;
Expand Down Expand Up @@ -411,6 +412,7 @@ pub async fn execute_memory_extraction_pipeline(
endpoint: String,
model: String,
db_path: PathBuf,
correction_signal: Option<memory_agent::CorrectionSignal>,
) -> Result<Changeset, String> {
// 1. Load and filter chat history synchronously within scoped block to drop connection before await
let chat_history = {
Expand All @@ -421,7 +423,7 @@ pub async fn execute_memory_extraction_pipeline(
"SELECT id, role, content, node_refs, created_at
FROM session_messages
WHERE session_id = 'default-session'
ORDER BY created_at ASC, id ASC;",
ORDER BY created_at ASC, rowid ASC;",
)
.map_err(|err| format!("Failed preparing session_messages query: {err}"))?;

Expand Down Expand Up @@ -592,17 +594,23 @@ pub async fn execute_memory_extraction_pipeline(
// 7. Reuse a single connection for changeset build, persist, and retrieval
let mut conn = open_connection(&db_path)?;

let changeset_id = {
let changeset_id: String = if let Some(ref signal) = correction_signal {
let (id, _amended) = memory_agent::amendment::amend_or_create_changeset(
&mut conn,
&candidates,
"default-session",
&model,
signal,
)?;
id
} else {
let tx = conn
.transaction()
.map_err(|err| format!("Failed to start transaction: {err}"))?;

let pending_changeset =
memory_agent::changeset::build_changeset(&tx, &candidates, "default-session")?;

let persisted_id =
memory_agent::persistence::persist_changeset(&tx, &pending_changeset, Some(&model))?;

tx.commit()
.map_err(|err| format!("Failed to commit transaction: {err}"))?;
persisted_id
Expand All @@ -626,7 +634,23 @@ async fn memory_extract(

// 2. Execute shared pipeline
let db_path = state.db_path.clone();
execute_memory_extraction_pipeline(provider, endpoint, model, db_path).await
execute_memory_extraction_pipeline(provider, endpoint, model, db_path, None).await
}

fn fetch_latest_user_message(
conn: &Connection,
session_id: &str,
) -> Result<Option<String>, String> {
conn.query_row(
"SELECT content FROM session_messages
WHERE session_id = ?1 AND role = 'user'
ORDER BY created_at DESC, rowid DESC
LIMIT 1;",
[session_id],
|row| row.get(0),
Comment thread
AashishH15 marked this conversation as resolved.
)
.optional()
.map_err(|err| format!("Failed querying latest user message: {err}"))
}

#[tauri::command]
Expand Down Expand Up @@ -657,7 +681,22 @@ async fn memory_extract_if_ready(
memory_agent::trigger::align_last_extract_count(&conn, current_message_count)?;

// 5. Check trigger
let ready = memory_agent::trigger::should_extract(&conn, session_id)?;
let mut ready = memory_agent::trigger::should_extract(&conn, session_id)?;
let mut correction_signal = None;

// Always scan the latest user message for a correction signal to ensure
// corrections are correctly routed to the amendment engine instead of creating duplicates,
// even if the standard extraction cooldown has already elapsed.
let latest_user_msg = fetch_latest_user_message(&conn, session_id)?;
if let Some(msg_content) = latest_user_msg {
let signal =
memory_agent::trigger::should_extract_correction(&conn, session_id, &msg_content)?;
if signal.is_some() {
ready = true;
correction_signal = signal;
}
}
Comment thread
AashishH15 marked this conversation as resolved.
Comment thread
AashishH15 marked this conversation as resolved.

if !ready {
return Ok(None);
}
Expand All @@ -667,8 +706,14 @@ async fn memory_extract_if_ready(

// 5. Execute shared pipeline (capture result without early-returning on error,
// so we always mark the extraction as attempted and respect cooldown windows)
let pipeline_result =
execute_memory_extraction_pipeline(provider, endpoint, model, db_path.clone()).await;
let pipeline_result = execute_memory_extraction_pipeline(
provider,
endpoint,
model,
db_path.clone(),
correction_signal,
)
.await;

// 6. Mark extraction complete/attempted *before* propagating any error,
// so that should_extract respects the 6-message and 2-minute cooldown
Expand Down Expand Up @@ -3522,6 +3567,7 @@ pub fn run() {
save_markdown_file,
memory_extract,
memory_extract_if_ready,
memory_extract_force,
changeset_count_pending,
changeset_list_pending,
changeset_list_items,
Expand All @@ -3535,3 +3581,70 @@ pub fn run() {
std::process::exit(1);
});
}

#[tauri::command]
async fn memory_extract_force(
provider: String,
endpoint: String,
model: String,
state: tauri::State<'_, AppState>,
) -> Result<Changeset, String> {
check_rate_limit("memory_agent")?;
let db_path = state.db_path.clone();
let conn = open_connection(&db_path)?;

// Verify minimum message threshold (at least 3 messages)
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM session_messages WHERE session_id = 'default-session';",
[],
|row| row.get(0),
)
.map_err(|err| format!("Failed querying message count: {err}"))?;
if count < 3 {
return Err("Need at least 3 messages to extract memory.".to_string());
}

// Since memory_extract_force is a manual bypass/trigger, check if there's a correction
// signal in the latest user message to see if we should run it as a correction.
let latest_user_msg = fetch_latest_user_message(&conn, "default-session")?;
let correction_signal = if let Some(msg_content) = latest_user_msg {
memory_agent::trigger::should_extract_correction(&conn, "default-session", &msg_content)?
} else {
None
};

drop(conn);

// Execute pipeline with the detected correction signal
let result = execute_memory_extraction_pipeline(
provider,
endpoint,
model,
db_path.clone(),
correction_signal,
)
.await;

// Mark extraction complete
let conn = open_connection(&db_path)?;
memory_agent::trigger::mark_extraction_complete(&conn, count)?;

result
}

pub async fn test_helper_memory_extract_force(
provider: String,
endpoint: String,
model: String,
db_path: std::path::PathBuf,
) -> Result<ipc_types::Changeset, String> {
use tauri::Manager;
let app = tauri::test::mock_app();
app.manage(AppState {
db_path,
redacted_session_key: std::sync::Mutex::new(None),
});
let state = app.state::<AppState>();
memory_extract_force(provider, endpoint, model, state).await
}
Loading