forked from Rockachopa/Timmy-time-dashboard
@@ -17,6 +17,7 @@ from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from config import APP_START_TIME as _START_TIME
|
||||
from config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -52,7 +53,6 @@ class HealthStatus(BaseModel):
|
||||
|
||||
|
||||
# Simple uptime tracking
|
||||
from config import APP_START_TIME as _START_TIME
|
||||
|
||||
# Ollama health cache (30-second TTL)
|
||||
_ollama_cache: DependencyStatus | None = None
|
||||
|
||||
@@ -54,7 +54,7 @@ def _ensure_schema(conn: sqlite3.Connection) -> None:
|
||||
access_count INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
# Create indexes for efficient querying
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_time ON memories(created_at)")
|
||||
@@ -69,7 +69,7 @@ def _ensure_schema(conn: sqlite3.Connection) -> None:
|
||||
|
||||
def _migrate_schema(conn: sqlite3.Connection) -> None:
|
||||
"""Migrate from old three-table schema to unified memories table.
|
||||
|
||||
|
||||
Migration paths:
|
||||
- episodes table -> memories (context_type -> memory_type)
|
||||
- chunks table -> memories with memory_type='vault_chunk'
|
||||
@@ -94,7 +94,7 @@ def _migrate_schema(conn: sqlite3.Connection) -> None:
|
||||
try:
|
||||
cols = _get_table_columns(conn, "episodes")
|
||||
context_type_col = "context_type" if "context_type" in cols else "'conversation'"
|
||||
|
||||
|
||||
conn.execute(f"""
|
||||
INSERT INTO memories (
|
||||
id, content, memory_type, source, embedding,
|
||||
@@ -120,13 +120,15 @@ def _migrate_schema(conn: sqlite3.Connection) -> None:
|
||||
logger.info("Migration: Converting chunks table to memories")
|
||||
try:
|
||||
cols = _get_table_columns(conn, "chunks")
|
||||
|
||||
|
||||
id_col = "id" if "id" in cols else "CAST(rowid AS TEXT)"
|
||||
content_col = "content" if "content" in cols else "text"
|
||||
source_col = "filepath" if "filepath" in cols else ("source" if "source" in cols else "'vault'")
|
||||
source_col = (
|
||||
"filepath" if "filepath" in cols else ("source" if "source" in cols else "'vault'")
|
||||
)
|
||||
embedding_col = "embedding" if "embedding" in cols else "NULL"
|
||||
created_col = "created_at" if "created_at" in cols else "datetime('now')"
|
||||
|
||||
|
||||
conn.execute(f"""
|
||||
INSERT INTO memories (
|
||||
id, content, memory_type, source, embedding,
|
||||
@@ -166,7 +168,7 @@ get_conn = get_connection
|
||||
@dataclass
|
||||
class MemoryEntry:
|
||||
"""A memory entry with vector embedding.
|
||||
|
||||
|
||||
Note: The DB column is `memory_type` but this field is named `context_type`
|
||||
for backward API compatibility.
|
||||
"""
|
||||
|
||||
@@ -149,7 +149,7 @@ def _ensure_schema(conn: sqlite3.Connection) -> None:
|
||||
access_count INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
# Create indexes for efficient querying
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_time ON memories(created_at)")
|
||||
@@ -170,7 +170,7 @@ def _get_table_columns(conn: sqlite3.Connection, table_name: str) -> set[str]:
|
||||
|
||||
def _migrate_schema(conn: sqlite3.Connection) -> None:
|
||||
"""Migrate from old three-table schema to unified memories table.
|
||||
|
||||
|
||||
Migration paths:
|
||||
- episodes table -> memories (context_type -> memory_type)
|
||||
- chunks table -> memories with memory_type='vault_chunk'
|
||||
@@ -195,7 +195,7 @@ def _migrate_schema(conn: sqlite3.Connection) -> None:
|
||||
try:
|
||||
cols = _get_table_columns(conn, "episodes")
|
||||
context_type_col = "context_type" if "context_type" in cols else "'conversation'"
|
||||
|
||||
|
||||
conn.execute(f"""
|
||||
INSERT INTO memories (
|
||||
id, content, memory_type, source, embedding,
|
||||
@@ -221,13 +221,15 @@ def _migrate_schema(conn: sqlite3.Connection) -> None:
|
||||
logger.info("Migration: Converting chunks table to memories")
|
||||
try:
|
||||
cols = _get_table_columns(conn, "chunks")
|
||||
|
||||
|
||||
id_col = "id" if "id" in cols else "CAST(rowid AS TEXT)"
|
||||
content_col = "content" if "content" in cols else "text"
|
||||
source_col = "filepath" if "filepath" in cols else ("source" if "source" in cols else "'vault'")
|
||||
source_col = (
|
||||
"filepath" if "filepath" in cols else ("source" if "source" in cols else "'vault'")
|
||||
)
|
||||
embedding_col = "embedding" if "embedding" in cols else "NULL"
|
||||
created_col = "created_at" if "created_at" in cols else "datetime('now')"
|
||||
|
||||
|
||||
conn.execute(f"""
|
||||
INSERT INTO memories (
|
||||
id, content, memory_type, source, embedding,
|
||||
@@ -266,7 +268,7 @@ get_conn = get_connection
|
||||
@dataclass
|
||||
class MemoryEntry:
|
||||
"""A memory entry with vector embedding.
|
||||
|
||||
|
||||
Note: The DB column is `memory_type` but this field is named `context_type`
|
||||
for backward API compatibility.
|
||||
"""
|
||||
@@ -663,16 +665,16 @@ class HotMemory:
|
||||
return "\n".join(lines)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# Fallback to file if DB unavailable
|
||||
if self.path.exists():
|
||||
return self.path.read_text()
|
||||
|
||||
|
||||
return "# Timmy Hot Memory\n\nNo memories stored yet.\n"
|
||||
|
||||
def update_section(self, section: str, content: str) -> None:
|
||||
"""Update a specific section in MEMORY.md.
|
||||
|
||||
|
||||
DEPRECATED: Hot memory is now computed from the database.
|
||||
This method is kept for backward compatibility during transition.
|
||||
Use memory_write() to store facts in the database.
|
||||
@@ -681,7 +683,7 @@ class HotMemory:
|
||||
"HotMemory.update_section() is deprecated. "
|
||||
"Use memory_write() to store facts in the database."
|
||||
)
|
||||
|
||||
|
||||
# Keep file-writing for backward compatibility during transition
|
||||
# Guard against empty or excessively large writes
|
||||
if not content or not content.strip():
|
||||
@@ -719,11 +721,13 @@ class HotMemory:
|
||||
|
||||
def _create_default(self) -> None:
|
||||
"""Create default MEMORY.md if missing.
|
||||
|
||||
|
||||
DEPRECATED: Hot memory is now computed from the database.
|
||||
This method is kept for backward compatibility during transition.
|
||||
"""
|
||||
logger.debug("HotMemory._create_default() - creating default MEMORY.md for backward compatibility")
|
||||
logger.debug(
|
||||
"HotMemory._create_default() - creating default MEMORY.md for backward compatibility"
|
||||
)
|
||||
default_content = """# Timmy Hot Memory
|
||||
|
||||
> Working RAM — always loaded, ~300 lines max, pruned monthly
|
||||
@@ -839,7 +843,7 @@ class VaultMemory:
|
||||
|
||||
def update_user_profile(self, key: str, value: str) -> None:
|
||||
"""Update a field in user_profile.md.
|
||||
|
||||
|
||||
DEPRECATED: User profile updates should now use memory_write() to store
|
||||
facts in the database. This method is kept for backward compatibility.
|
||||
"""
|
||||
@@ -958,7 +962,9 @@ class SemanticMemory:
|
||||
access_count INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)")
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)"
|
||||
)
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_time ON memories(created_at)")
|
||||
conn.execute("CREATE INDEX IF NOT EXISTS idx_memories_source ON memories(source)")
|
||||
conn.commit()
|
||||
@@ -986,7 +992,7 @@ class SemanticMemory:
|
||||
# Check if already indexed with same hash
|
||||
cursor = conn.execute(
|
||||
"SELECT metadata FROM memories WHERE source = ? AND memory_type = 'vault_chunk' LIMIT 1",
|
||||
(str(filepath),)
|
||||
(str(filepath),),
|
||||
)
|
||||
existing = cursor.fetchone()
|
||||
if existing and existing[0]:
|
||||
@@ -1000,7 +1006,7 @@ class SemanticMemory:
|
||||
# Delete old chunks for this file
|
||||
conn.execute(
|
||||
"DELETE FROM memories WHERE source = ? AND memory_type = 'vault_chunk'",
|
||||
(str(filepath),)
|
||||
(str(filepath),),
|
||||
)
|
||||
|
||||
# Split into chunks (paragraphs)
|
||||
|
||||
@@ -138,7 +138,9 @@ async def chat_with_tools(message: str, session_id: str | None = None):
|
||||
try:
|
||||
run_output = await agent.arun(message, stream=False, session_id=sid)
|
||||
# Record Timmy response after getting it
|
||||
response_text = run_output.content if hasattr(run_output, "content") and run_output.content else ""
|
||||
response_text = (
|
||||
run_output.content if hasattr(run_output, "content") and run_output.content else ""
|
||||
)
|
||||
session_logger.record_message("timmy", response_text)
|
||||
session_logger.flush()
|
||||
return run_output
|
||||
|
||||
@@ -77,7 +77,7 @@ async def test_chat_records_error_on_exception():
|
||||
):
|
||||
from timmy.session import chat
|
||||
|
||||
result = await chat("Hi Timmy")
|
||||
await chat("Hi Timmy")
|
||||
|
||||
# Verify error was recorded
|
||||
mock_session_logger.record_error.assert_called_once()
|
||||
@@ -101,7 +101,7 @@ async def test_chat_records_error_on_connection_error():
|
||||
):
|
||||
from timmy.session import chat
|
||||
|
||||
result = await chat("Hi Timmy")
|
||||
await chat("Hi Timmy")
|
||||
|
||||
# Verify error was recorded
|
||||
mock_session_logger.record_error.assert_called_once()
|
||||
@@ -204,7 +204,7 @@ async def test_chat_with_tools_records_error():
|
||||
):
|
||||
from timmy.session import chat_with_tools
|
||||
|
||||
result = await chat_with_tools("Use a tool")
|
||||
await chat_with_tools("Use a tool")
|
||||
|
||||
# Verify error was recorded
|
||||
mock_session_logger.record_error.assert_called_once()
|
||||
@@ -271,7 +271,7 @@ async def test_continue_chat_records_error():
|
||||
):
|
||||
from timmy.session import continue_chat
|
||||
|
||||
result = await continue_chat(mock_run_output)
|
||||
await continue_chat(mock_run_output)
|
||||
|
||||
# Verify error was recorded
|
||||
mock_session_logger.record_error.assert_called_once()
|
||||
|
||||
@@ -292,7 +292,7 @@ class TestSemanticMemory:
|
||||
conn = sqlite3.connect(str(mem.db_path))
|
||||
cursor = conn.execute(
|
||||
"SELECT COUNT(*) FROM memories WHERE source = ? AND memory_type = 'vault_chunk'",
|
||||
(str(md_file),)
|
||||
(str(md_file),),
|
||||
)
|
||||
stored_count = cursor.fetchone()[0]
|
||||
conn.close()
|
||||
|
||||
Reference in New Issue
Block a user