bone-agent 1.3.2 → 1.3.3
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/README.md +2 -2
- package/config.yaml.example +8 -0
- package/package.json +3 -2
- package/prompts/main/ask_questions.md +31 -0
- package/prompts/main/batch_independent_calls.md +5 -0
- package/prompts/main/casual_interactions.md +11 -0
- package/prompts/main/code_references.md +8 -0
- package/prompts/main/communication_style.md +12 -0
- package/prompts/main/context_reliability.md +12 -0
- package/prompts/main/conversational_tool_calling.md +15 -0
- package/prompts/main/dream.md +36 -0
- package/prompts/main/editing_pattern.md +13 -0
- package/prompts/main/error_handling.md +6 -0
- package/prompts/main/exploration_pattern.md +21 -0
- package/prompts/main/intro.md +1 -0
- package/prompts/main/obsidian.md +16 -0
- package/prompts/main/obsidian_project.md +79 -0
- package/prompts/main/professional_objectivity.md +3 -0
- package/prompts/main/targeted_searching.md +10 -0
- package/prompts/main/task_lists_pattern.md +8 -0
- package/prompts/main/temp_folder.md +9 -0
- package/prompts/main/think_before_acting.md +10 -0
- package/prompts/main/tone_and_style.md +4 -0
- package/prompts/main/tool_preferences.md +24 -0
- package/prompts/main/trust_subagent_context.md +21 -0
- package/prompts/main/when_to_use_sub_agent.md +7 -0
- package/prompts/micro/ask_questions.md +1 -0
- package/prompts/micro/batch_independent_calls.md +1 -0
- package/prompts/micro/casual_interactions.md +1 -0
- package/prompts/micro/code_references.md +1 -0
- package/prompts/micro/communication_style.md +1 -0
- package/prompts/micro/context_reliability.md +1 -0
- package/prompts/micro/conversational_tool_calling.md +1 -0
- package/prompts/micro/editing_pattern.md +1 -0
- package/prompts/micro/error_handling.md +1 -0
- package/prompts/micro/exploration_pattern.md +1 -0
- package/prompts/micro/intro.md +1 -0
- package/prompts/micro/obsidian.md +4 -0
- package/prompts/micro/obsidian_project.md +5 -0
- package/prompts/micro/professional_objectivity.md +1 -0
- package/prompts/micro/targeted_searching.md +1 -0
- package/prompts/micro/task_lists_pattern.md +1 -0
- package/prompts/micro/temp_folder.md +1 -0
- package/prompts/micro/think_before_acting.md +5 -0
- package/prompts/micro/tone_and_style.md +1 -0
- package/prompts/micro/tool_preferences.md +1 -0
- package/prompts/micro/trust_subagent_context.md +1 -0
- package/prompts/micro/when_to_use_sub_agent.md +1 -0
- package/src/core/agentic.py +1 -73
- package/src/core/chat_manager.py +42 -7
- package/src/core/config_manager.py +6 -0
- package/src/core/cron.py +57 -2
- package/src/core/memory.py +3 -90
- package/src/llm/config.py +28 -2
- package/src/llm/prompts.py +251 -497
- package/src/llm/providers.py +25 -6
- package/src/llm/token_tracker.py +17 -1
- package/src/tools/edit.py +8 -6
- package/src/tools/helpers/path_resolver.py +18 -12
- package/src/tools/rg_search.py +97 -30
- package/src/ui/commands.py +120 -5
- package/src/ui/displays.py +1 -0
- package/src/ui/main.py +1 -0
- package/src/utils/settings.py +19 -2
- package/src/utils/user_message_logger.py +120 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Lightweight user-message logger for the dream memory system.
|
|
2
|
+
|
|
3
|
+
Appends one JSONL line per user message, one file per day per project.
|
|
4
|
+
Always on by default — no toggle needed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Base directory for daily message logs
|
|
16
|
+
CONVERSATIONS_DIR = Path.home() / ".bone" / "conversations"
|
|
17
|
+
RETENTION_DAYS = 7
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _project_suffix(project_dir: Path) -> str:
|
|
21
|
+
"""Generate a short suffix from a project directory path.
|
|
22
|
+
|
|
23
|
+
Format: {dirname}_{first 6 chars of SHA256(path)}
|
|
24
|
+
Avoids collisions between repos with the same folder name.
|
|
25
|
+
"""
|
|
26
|
+
path_str = str(project_dir.resolve())
|
|
27
|
+
h = hashlib.sha256(path_str.encode()).hexdigest()[:6]
|
|
28
|
+
return f"{project_dir.name}_{h}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
PROJECT_INDEX_FILE = CONVERSATIONS_DIR / ".project_index.jsonl"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _register_project(key: str, project_dir: Path) -> None:
|
|
35
|
+
"""Append a key→path mapping to the project index if not already present."""
|
|
36
|
+
resolved = str(project_dir.resolve())
|
|
37
|
+
# Check if this key already maps to this path
|
|
38
|
+
if PROJECT_INDEX_FILE.exists():
|
|
39
|
+
with open(PROJECT_INDEX_FILE, "r", encoding="utf-8") as f:
|
|
40
|
+
for line in f:
|
|
41
|
+
try:
|
|
42
|
+
entry = json.loads(line)
|
|
43
|
+
except json.JSONDecodeError:
|
|
44
|
+
continue
|
|
45
|
+
if entry.get("key") == key and entry.get("path") == resolved:
|
|
46
|
+
return # Already indexed
|
|
47
|
+
PROJECT_INDEX_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
with open(PROJECT_INDEX_FILE, "a", encoding="utf-8") as f:
|
|
49
|
+
f.write(json.dumps({"key": key, "path": resolved}) + "\n")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class UserMessageLogger:
|
|
53
|
+
"""Logs user messages to daily JSONL files for later dream processing.
|
|
54
|
+
|
|
55
|
+
When a project_dir is provided, messages go to a per-project file:
|
|
56
|
+
{date}__{dirname}_{hash}.jsonl
|
|
57
|
+
Without a project_dir, messages go to the catch-all:
|
|
58
|
+
{date}.jsonl
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, conversations_dir: Path | None = None):
|
|
62
|
+
self._dir = conversations_dir or CONVERSATIONS_DIR
|
|
63
|
+
self._dir.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
|
|
65
|
+
def log_user_message(self, content: str, project_dir: Path | None = None) -> None:
|
|
66
|
+
"""Append a single user message to today's JSONL file.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
content: The user message text.
|
|
70
|
+
project_dir: Optional project root directory. If provided,
|
|
71
|
+
messages are written to a per-project file.
|
|
72
|
+
|
|
73
|
+
Opens in append mode and flushes immediately for crash safety.
|
|
74
|
+
Each message is one self-contained JSON line.
|
|
75
|
+
"""
|
|
76
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
77
|
+
if project_dir:
|
|
78
|
+
suffix = _project_suffix(project_dir)
|
|
79
|
+
_register_project(suffix, project_dir)
|
|
80
|
+
filepath = self._dir / f"{today}__{suffix}.jsonl"
|
|
81
|
+
else:
|
|
82
|
+
filepath = self._dir / f"{today}.jsonl"
|
|
83
|
+
entry = {"ts": datetime.now().isoformat(), "msg": content}
|
|
84
|
+
with open(filepath, "a", encoding="utf-8") as f:
|
|
85
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def cleanup_old_files(directory: Path | None = None, retention_days: int = RETENTION_DAYS) -> int:
|
|
89
|
+
"""Delete JSONL files older than retention_days. Returns count of files removed."""
|
|
90
|
+
target_dir = directory or CONVERSATIONS_DIR
|
|
91
|
+
if not target_dir.exists():
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
cutoff = datetime.now() - timedelta(days=retention_days)
|
|
95
|
+
removed = 0
|
|
96
|
+
surviving = set()
|
|
97
|
+
for f in target_dir.glob("*.jsonl"):
|
|
98
|
+
if f.stat().st_mtime < cutoff.timestamp():
|
|
99
|
+
f.unlink()
|
|
100
|
+
removed += 1
|
|
101
|
+
logger.debug("Removed old conversation log: %s", f.name)
|
|
102
|
+
else:
|
|
103
|
+
surviving.add(f.name)
|
|
104
|
+
|
|
105
|
+
# Prune stale entries from the project index
|
|
106
|
+
index_file = target_dir / ".project_index.jsonl"
|
|
107
|
+
if index_file.exists():
|
|
108
|
+
kept: list[str] = []
|
|
109
|
+
for line in index_file.read_text(encoding="utf-8").splitlines():
|
|
110
|
+
try:
|
|
111
|
+
entry = json.loads(line)
|
|
112
|
+
except json.JSONDecodeError:
|
|
113
|
+
continue
|
|
114
|
+
key = entry.get("key", "")
|
|
115
|
+
# Keep entry if any file matching its key still exists
|
|
116
|
+
if any(key in name for name in surviving):
|
|
117
|
+
kept.append(line)
|
|
118
|
+
index_file.write_text("\n".join(kept) + ("\n" if kept else ""), encoding="utf-8")
|
|
119
|
+
|
|
120
|
+
return removed
|