kore-memory 2.3.0__tar.gz → 2.4.0__tar.gz
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.
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.github/workflows/ci.yml +8 -2
- {kore_memory-2.3.0 → kore_memory-2.4.0}/CHANGELOG.md +29 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/PKG-INFO +1 -1
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/config.py +1 -1
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/database.py +5 -3
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/filesystem_overlay.py +1 -4
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/mcp_server.py +59 -7
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/repository/search.py +2 -1
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/templates/dashboard.html +6 -2
- {kore_memory-2.3.0 → kore_memory-2.4.0}/pyproject.toml +1 -1
- {kore_memory-2.3.0 → kore_memory-2.4.0}/scripts/assert_benchmarks.py +22 -5
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/package.json +1 -1
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.github/workflows/benchmark.yml +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.github/workflows/build-sdk.yml +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.github/workflows/publish.yml +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/.gitignore +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/CONTRIBUTING.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/LICENSE +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/MIGRATION-v0.6.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/README.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/article-devto.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/assets/favicon.svg +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/assets/logo.svg +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/docs/competitive-analysis-2025.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/docs/quickstart-v2.1.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/docs/v1.0-roadmap.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/examples/async_usage.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/examples/basic_usage.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/examples/langchain_example.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/hatch_build.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore-daemon.sh +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/__init__.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/acl.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/analytics.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/audit.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/auth.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/auto_tuner.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/cli.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/client.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/compressor.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/conflict_detector.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/context_assembler.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/dashboard.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/decay.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/embedder.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/events.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/integrations/__init__.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/integrations/crewai.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/integrations/entities.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/integrations/langchain.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/integrations/openai_agents.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/integrations/pydantic_ai.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/main.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/models.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/plugins.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/py.typed +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/ranking.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/repository/__init__.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/repository/graph.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/repository/lifecycle.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/repository/memory.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/repository/sessions.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/scorer.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/summarizer.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/vector_index.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/kore_memory/welcome.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/presets/claude-code/README.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/presets/claude-code/mcp.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/presets/cursor/README.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/presets/cursor/mcp.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/requirements.txt +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/scripts/import_memory.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/.gitignore +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/README.md +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/package-lock.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/src/client.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/src/errors.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/src/index.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/src/types.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/tests/client.test.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/tests/errors.test.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/tests/helpers.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/tsconfig.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/tsup.config.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/sdk/js/vitest.config.ts +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/start.sh +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/test_pagination_fix.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/__init__.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/__init__.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/conftest_bench.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/datasets/dataset_a_temporal.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/datasets/dataset_b_conflicts.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/datasets/dataset_c_coding.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/datasets/dataset_d_graph.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/datasets/dataset_e_context.json +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/datasets/loaders.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/benchmarks/test_benchmarks.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/conftest.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_api.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_audit.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_auth_events.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_auto_tuner.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_cli.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_client.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_client_sync.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_conflict_detection.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_crewai.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_dashboard.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_entities.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_langchain.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_mcp.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_ranking_engine.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_sessions.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_v11_fixes.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_v12_features.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_v21_temporal.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_v2_features.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_wave3_graph.py +0 -0
- {kore_memory-2.3.0 → kore_memory-2.4.0}/tests/test_wave3_overlay.py +0 -0
|
@@ -150,8 +150,14 @@ jobs:
|
|
|
150
150
|
run: bandit -r kore_memory/ -c pyproject.toml
|
|
151
151
|
|
|
152
152
|
- name: Run pip-audit (CVE check)
|
|
153
|
-
#
|
|
154
|
-
|
|
153
|
+
# Audita solo le dipendenze installate, non il package locale (non è su PyPI)
|
|
154
|
+
# Usiamo -r requirements-audit.txt generato al volo per evitare problemi con editable installs
|
|
155
|
+
run: |
|
|
156
|
+
pip list --format=freeze \
|
|
157
|
+
| grep -v "^kore-memory==" \
|
|
158
|
+
| grep -v "^kore_memory==" \
|
|
159
|
+
> /tmp/audit_reqs.txt
|
|
160
|
+
pip-audit -r /tmp/audit_reqs.txt
|
|
155
161
|
|
|
156
162
|
lint:
|
|
157
163
|
runs-on: ubuntu-latest
|
|
@@ -11,6 +11,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [2.4.0] - 2026-04-14
|
|
15
|
+
|
|
16
|
+
### Theme: "Dashboard Fixes + MCP Auto-Session"
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
#### MCP Auto-Session
|
|
21
|
+
- **Auto-creazione sessione** all'avvio di `kore-mcp`: il primo `memory_save` per ogni `agent_id` crea automaticamente una sessione con ID `kore-mcp-{agent_id}-{YYYYMMDD-HHMMSS}`
|
|
22
|
+
- **Thread-safe**: creazione lazy con double-checked locking (`_get_or_create_session`)
|
|
23
|
+
- **atexit handler**: chiude tutte le sessioni aperte alla terminazione del processo MCP
|
|
24
|
+
- **Propagazione automatica** a `memory_save`, `memory_save_batch`, `memory_save_decision`, `memory_log_regression`
|
|
25
|
+
- La risposta di `memory_save` include ora `session_id` per tracciabilità
|
|
26
|
+
- La tab **Sessions** della dashboard si popola organicamente con una riga per ogni conversazione Claude/MCP
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
#### Dashboard
|
|
31
|
+
- **Tab Memories**: `_count_active_memories` non gestiva il wildcard `q=*` come `_fts_search` — con query `*` costruiva `LIKE "%*%"` (cerca il carattere asterisco) invece di `LIKE "%%"` (match all). `total` tornava 2 invece di 40
|
|
32
|
+
- **Checkbox Semantic**: rimosso `checked` di default — gli utenti senza `sentence-transformers` ottenevano risultati imprevedibili; il default sicuro è `semantic=false`
|
|
33
|
+
- **Tab Timeline**: mancava `case 'timeline':` in `loadPageData()` — la tab si apriva con `#timeline-list` completamente vuoto, senza messaggi né dati. Fix: auto-carica le ultime memorie in ordine cronologico (`subject=*`) con header contestuale
|
|
34
|
+
|
|
35
|
+
#### Database
|
|
36
|
+
- **`idx_relations_strength`**: l'indice era nell'`executescript()` iniziale prima che la colonna `strength` venisse aggiunta via ALTER TABLE. Su DB pre-esistenti causava `no such column: strength` al startup. Spostato in `executescript()` separato dopo `_v23_relation_migrations`
|
|
37
|
+
|
|
38
|
+
### Tests
|
|
39
|
+
- Suite invariata: **572 test**, coverage ≥ 88%
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
14
43
|
## [2.3.0] - 2026-04-14
|
|
15
44
|
|
|
16
45
|
### Theme: "Wave 3 — Graph Engine + Filesystem Overlay"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kore-memory
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: The memory layer that thinks like a human: remembers what matters, forgets what doesn't, and never calls home.
|
|
5
5
|
Project-URL: Homepage, https://github.com/auriti-labs/kore-memory
|
|
6
6
|
Project-URL: Documentation, https://auritidesign.it/docs
|
|
@@ -215,7 +215,7 @@ def init_db() -> None:
|
|
|
215
215
|
);
|
|
216
216
|
CREATE INDEX IF NOT EXISTS idx_relations_source ON memory_relations (source_id);
|
|
217
217
|
CREATE INDEX IF NOT EXISTS idx_relations_target ON memory_relations (target_id);
|
|
218
|
-
|
|
218
|
+
-- idx_relations_strength creato dopo la migrazione v2.3 (colonna strength)
|
|
219
219
|
|
|
220
220
|
-- Audit / event log
|
|
221
221
|
CREATE TABLE IF NOT EXISTS event_logs (
|
|
@@ -308,9 +308,11 @@ def init_db() -> None:
|
|
|
308
308
|
for col, sql in _v23_relation_migrations.items():
|
|
309
309
|
if col not in relation_cols:
|
|
310
310
|
conn.execute(sql)
|
|
311
|
-
|
|
311
|
+
# Indice su strength — creato qui (dopo ALTER TABLE) per DB pre-esistenti
|
|
312
|
+
# e per DB nuovi (la CREATE TABLE usa IF NOT EXISTS, l'indice viene saltato sopra)
|
|
313
|
+
conn.executescript("""
|
|
312
314
|
CREATE INDEX IF NOT EXISTS idx_relations_strength
|
|
313
|
-
ON memory_relations (strength DESC)
|
|
315
|
+
ON memory_relations (strength DESC);
|
|
314
316
|
""")
|
|
315
317
|
|
|
316
318
|
|
|
@@ -287,10 +287,7 @@ def index_files(
|
|
|
287
287
|
|
|
288
288
|
for chunk_content_text, chunk_idx in chunks:
|
|
289
289
|
chunk_label = f" [{chunk_idx + 1}/{len(chunks)}]" if len(chunks) > 1 else ""
|
|
290
|
-
enriched_content = (
|
|
291
|
-
f"[File: {Path(filepath).name}{chunk_label}]\n"
|
|
292
|
-
f"{chunk_content_text.strip()}"
|
|
293
|
-
)
|
|
290
|
+
enriched_content = f"[File: {Path(filepath).name}{chunk_label}]\n{chunk_content_text.strip()}"
|
|
294
291
|
# Tronca a 4000 chars (limite modello)
|
|
295
292
|
enriched_content = enriched_content[:4000]
|
|
296
293
|
|
|
@@ -11,9 +11,12 @@ Usage:
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import atexit
|
|
14
15
|
import logging
|
|
15
16
|
import re as _re
|
|
17
|
+
import threading
|
|
16
18
|
import time as _time
|
|
19
|
+
from datetime import datetime
|
|
17
20
|
|
|
18
21
|
from mcp.server.fastmcp import FastMCP
|
|
19
22
|
|
|
@@ -24,7 +27,9 @@ from .repository import (
|
|
|
24
27
|
add_relation,
|
|
25
28
|
add_tags,
|
|
26
29
|
cleanup_expired,
|
|
30
|
+
create_session,
|
|
27
31
|
delete_memory,
|
|
32
|
+
end_session,
|
|
28
33
|
export_memories,
|
|
29
34
|
get_timeline,
|
|
30
35
|
import_memories,
|
|
@@ -37,8 +42,9 @@ from .repository import (
|
|
|
37
42
|
|
|
38
43
|
logger = logging.getLogger(__name__)
|
|
39
44
|
|
|
40
|
-
# Timestamp di avvio per il calcolo dell'uptime
|
|
45
|
+
# Timestamp di avvio per il calcolo dell'uptime e dell'ID sessione
|
|
41
46
|
_SERVER_START_TIME = _time.monotonic()
|
|
47
|
+
_SESSION_TIMESTAMP = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
42
48
|
|
|
43
49
|
# Initialize DB before any operation
|
|
44
50
|
init_db()
|
|
@@ -50,6 +56,46 @@ mcp = FastMCP(
|
|
|
50
56
|
|
|
51
57
|
_SAFE_AGENT_RE = _re.compile(r"[^a-zA-Z0-9_\-]")
|
|
52
58
|
|
|
59
|
+
# ── Auto-Session ─────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
# Sessioni attive per agent_id — create in modo lazy al primo save
|
|
62
|
+
_agent_sessions: dict[str, str] = {}
|
|
63
|
+
_session_lock = threading.Lock()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _get_or_create_session(agent_id: str) -> str:
|
|
67
|
+
"""Restituisce la session_id corrente per l'agent, creandola se necessario."""
|
|
68
|
+
if agent_id in _agent_sessions:
|
|
69
|
+
return _agent_sessions[agent_id]
|
|
70
|
+
with _session_lock:
|
|
71
|
+
# Double-check dopo aver acquisito il lock
|
|
72
|
+
if agent_id in _agent_sessions:
|
|
73
|
+
return _agent_sessions[agent_id]
|
|
74
|
+
session_id = f"kore-mcp-{agent_id}-{_SESSION_TIMESTAMP}"
|
|
75
|
+
ts = _SESSION_TIMESTAMP
|
|
76
|
+
date_str = f"{ts[:4]}-{ts[4:6]}-{ts[6:8]} {ts[9:11]}:{ts[11:13]}:{ts[13:15]}"
|
|
77
|
+
title = f"MCP — {date_str}"
|
|
78
|
+
try:
|
|
79
|
+
create_session(session_id, agent_id=agent_id, title=title)
|
|
80
|
+
except Exception:
|
|
81
|
+
pass # DB potrebbe già avere la sessione (restart rapido)
|
|
82
|
+
_agent_sessions[agent_id] = session_id
|
|
83
|
+
logger.info("Auto-sessione creata: %s (agent=%s)", session_id, agent_id)
|
|
84
|
+
return session_id
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _close_all_sessions() -> None:
|
|
88
|
+
"""Chiude tutte le sessioni aperte all'uscita del server MCP."""
|
|
89
|
+
for agent_id, session_id in _agent_sessions.items():
|
|
90
|
+
try:
|
|
91
|
+
end_session(session_id, agent_id=agent_id)
|
|
92
|
+
logger.info("Sessione chiusa: %s (agent=%s)", session_id, agent_id)
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
atexit.register(_close_all_sessions)
|
|
98
|
+
|
|
53
99
|
|
|
54
100
|
def _sanitize_agent_id(agent_id: str) -> str:
|
|
55
101
|
"""Sanitize agent_id: only alphanumeric characters, dashes and underscores, max 64 chars."""
|
|
@@ -81,9 +127,11 @@ def memory_save(
|
|
|
81
127
|
Importance is auto-calculated if 0 or not specified (1-5 = explicit).
|
|
82
128
|
Categories: general, project, trading, finance, person, preference, task, decision.
|
|
83
129
|
"""
|
|
130
|
+
safe_agent = _sanitize_agent_id(agent_id)
|
|
131
|
+
session_id = _get_or_create_session(safe_agent)
|
|
84
132
|
req = MemorySaveRequest(content=content, category=category, importance=importance or None)
|
|
85
|
-
mem_id, imp, _ = save_memory(req, agent_id=
|
|
86
|
-
return {"id": mem_id, "importance": imp, "message": "Memory saved"}
|
|
133
|
+
mem_id, imp, _ = save_memory(req, agent_id=safe_agent, session_id=session_id)
|
|
134
|
+
return {"id": mem_id, "importance": imp, "session_id": session_id, "message": "Memory saved"}
|
|
87
135
|
|
|
88
136
|
|
|
89
137
|
@mcp.tool()
|
|
@@ -248,6 +296,8 @@ def memory_save_batch(
|
|
|
248
296
|
Optional fields: category (default 'general'), importance (None=auto, 1-5=explicit).
|
|
249
297
|
Maximum 100 memories per batch.
|
|
250
298
|
"""
|
|
299
|
+
safe_agent = _sanitize_agent_id(agent_id)
|
|
300
|
+
session_id = _get_or_create_session(safe_agent)
|
|
251
301
|
saved = []
|
|
252
302
|
errors = 0
|
|
253
303
|
for mem in memories[:100]:
|
|
@@ -261,11 +311,11 @@ def memory_save_batch(
|
|
|
261
311
|
category=mem.get("category", "general"),
|
|
262
312
|
importance=raw_imp if raw_imp and raw_imp >= 1 else None,
|
|
263
313
|
)
|
|
264
|
-
mem_id, imp, _ = save_memory(req, agent_id=
|
|
314
|
+
mem_id, imp, _ = save_memory(req, agent_id=safe_agent, session_id=session_id)
|
|
265
315
|
saved.append({"id": mem_id, "importance": imp})
|
|
266
316
|
except Exception:
|
|
267
317
|
errors += 1
|
|
268
|
-
return {"saved": saved, "total": len(saved), "errors": errors}
|
|
318
|
+
return {"saved": saved, "total": len(saved), "errors": errors, "session_id": session_id}
|
|
269
319
|
|
|
270
320
|
|
|
271
321
|
@mcp.tool()
|
|
@@ -381,13 +431,14 @@ def memory_save_decision(
|
|
|
381
431
|
"still_valid": True,
|
|
382
432
|
}
|
|
383
433
|
sanitized = _sanitize_agent_id(f"{agent_id}/{repo}" if repo else agent_id)
|
|
434
|
+
session_id = _get_or_create_session(sanitized)
|
|
384
435
|
req = MemorySaveRequest(
|
|
385
436
|
content=content,
|
|
386
437
|
category="architectural_decision",
|
|
387
438
|
importance=4,
|
|
388
439
|
metadata=metadata,
|
|
389
440
|
)
|
|
390
|
-
mem_id, imp, conflicts = save_memory(req, agent_id=sanitized)
|
|
441
|
+
mem_id, imp, conflicts = save_memory(req, agent_id=sanitized, session_id=session_id)
|
|
391
442
|
return {
|
|
392
443
|
"id": mem_id,
|
|
393
444
|
"importance": imp,
|
|
@@ -461,13 +512,14 @@ def memory_log_regression(
|
|
|
461
512
|
"test_ref": test_ref,
|
|
462
513
|
}
|
|
463
514
|
sanitized = _sanitize_agent_id(f"{agent_id}/{repo}" if repo else agent_id)
|
|
515
|
+
session_id = _get_or_create_session(sanitized)
|
|
464
516
|
req = MemorySaveRequest(
|
|
465
517
|
content=content,
|
|
466
518
|
category="regression_note",
|
|
467
519
|
importance=4,
|
|
468
520
|
metadata=metadata,
|
|
469
521
|
)
|
|
470
|
-
mem_id, imp, conflicts = save_memory(req, agent_id=sanitized)
|
|
522
|
+
mem_id, imp, conflicts = save_memory(req, agent_id=sanitized, session_id=session_id)
|
|
471
523
|
return {
|
|
472
524
|
"id": mem_id,
|
|
473
525
|
"importance": imp,
|
|
@@ -345,7 +345,8 @@ def _count_active_memories(
|
|
|
345
345
|
"""
|
|
346
346
|
params: dict = {"query": safe_query, "agent_id": agent_id}
|
|
347
347
|
else:
|
|
348
|
-
|
|
348
|
+
# Caso speciale: q=* → conta tutte le memorie attive (wildcard globale)
|
|
349
|
+
escaped = "" if query.strip() == "*" else query.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
349
350
|
sql = f"""
|
|
350
351
|
SELECT COUNT(*) FROM memories
|
|
351
352
|
WHERE content LIKE :query ESCAPE '\\'
|
|
@@ -569,7 +569,7 @@ button:focus:not(:focus-visible), a:focus:not(:focus-visible) { outline: none; }
|
|
|
569
569
|
<option value="decision">decision</option>
|
|
570
570
|
</select>
|
|
571
571
|
<label style="display:flex;align-items:center;gap:4px;font-size:12px;color:var(--text-muted);cursor:pointer">
|
|
572
|
-
<input type="checkbox" id="mem-semantic"
|
|
572
|
+
<input type="checkbox" id="mem-semantic"> Semantic
|
|
573
573
|
</label>
|
|
574
574
|
<button class="btn btn-primary" id="btn-new-memory">
|
|
575
575
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
@@ -1022,6 +1022,7 @@ function loadPageData(pageId) {
|
|
|
1022
1022
|
case 'memories': return loadMemories();
|
|
1023
1023
|
case 'tags': return loadTags();
|
|
1024
1024
|
case 'graph': return loadGraph();
|
|
1025
|
+
case 'timeline': return loadTimeline($('#timeline-search').value.trim() || '*');
|
|
1025
1026
|
case 'sessions': return loadSessions();
|
|
1026
1027
|
case 'analytics': return loadAnalytics();
|
|
1027
1028
|
case 'maintenance': return loadMaintenance();
|
|
@@ -1783,7 +1784,10 @@ function loadTimeline(subject) {
|
|
|
1783
1784
|
safeRender('#timeline-list', '<div class="empty-state"><p>No results found</p></div>');
|
|
1784
1785
|
return;
|
|
1785
1786
|
}
|
|
1786
|
-
var
|
|
1787
|
+
var isAll = (subject === '*');
|
|
1788
|
+
var html = isAll
|
|
1789
|
+
? '<div style="font-size:12px;color:var(--text-muted);padding:0 4px 12px">Showing ' + data.results.length + ' most recent memories — type a subject to filter</div>'
|
|
1790
|
+
: '';
|
|
1787
1791
|
data.results.forEach(function(m) {
|
|
1788
1792
|
html += '<div class="timeline-item">' +
|
|
1789
1793
|
'<div class="timeline-dot" style="background:' + (CAT_COLORS[m.category]||'var(--accent)') + '"></div>' +
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kore-memory"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.0"
|
|
8
8
|
description = "The memory layer that thinks like a human: remembers what matters, forgets what doesn't, and never calls home."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -35,15 +35,26 @@ THRESHOLDS = {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def load_results(path: str) -> dict:
|
|
39
|
-
"""Carica il file results.json da pytest o da report custom.
|
|
38
|
+
def load_results(path: str) -> dict | None:
|
|
39
|
+
"""Carica il file results.json da pytest o da report custom.
|
|
40
|
+
|
|
41
|
+
Ritorna None se il file non esiste (distinto da file vuoto/invalido).
|
|
42
|
+
Ritorna {} se il file esiste ma è vuoto o non contiene JSON valido.
|
|
43
|
+
"""
|
|
40
44
|
results_path = Path(path)
|
|
41
45
|
if not results_path.exists():
|
|
42
46
|
print(f"⚠️ File risultati non trovato: {path}")
|
|
43
47
|
print(" Generare con: pytest tests/benchmarks/ -v --benchmark-json=results.json")
|
|
48
|
+
return None
|
|
49
|
+
try:
|
|
50
|
+
content = results_path.read_text().strip()
|
|
51
|
+
if not content:
|
|
52
|
+
# File vuoto: pytest-benchmark non ha trovato benchmark mark → OK
|
|
53
|
+
return {}
|
|
54
|
+
return json.loads(content)
|
|
55
|
+
except json.JSONDecodeError:
|
|
56
|
+
print(f"⚠️ File risultati non valido (JSON malformato): {path}")
|
|
44
57
|
return {}
|
|
45
|
-
with results_path.open() as f:
|
|
46
|
-
return json.load(f)
|
|
47
58
|
|
|
48
59
|
|
|
49
60
|
def check_thresholds(results: dict) -> tuple[list[str], list[str]]:
|
|
@@ -99,7 +110,8 @@ def main():
|
|
|
99
110
|
|
|
100
111
|
results = load_results(args.results)
|
|
101
112
|
|
|
102
|
-
if
|
|
113
|
+
if results is None:
|
|
114
|
+
# File non esiste
|
|
103
115
|
if args.strict:
|
|
104
116
|
print("❌ File risultati non trovato (--strict mode)")
|
|
105
117
|
sys.exit(1)
|
|
@@ -108,6 +120,11 @@ def main():
|
|
|
108
120
|
print(" Questo non è bloccante in assenza di --strict")
|
|
109
121
|
sys.exit(0)
|
|
110
122
|
|
|
123
|
+
if not results:
|
|
124
|
+
# File esiste ma vuoto o senza benchmark — i test si sono eseguiti senza @benchmark mark
|
|
125
|
+
print("✅ Suite benchmark eseguita (nessun metric quantitativo nel report)")
|
|
126
|
+
sys.exit(0)
|
|
127
|
+
|
|
111
128
|
passed, failed = check_thresholds(results)
|
|
112
129
|
|
|
113
130
|
print("Soglie di blocco CI:")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|