bone-agent 1.3.1 → 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 +9 -78
- package/src/core/chat_manager.py +120 -108
- 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/tools/select_option.py +12 -5
- 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
package/src/ui/main.py
CHANGED
|
@@ -507,6 +507,7 @@ def main():
|
|
|
507
507
|
thinking_indicator.start()
|
|
508
508
|
INPUT_BLOCKED['blocked'] = True
|
|
509
509
|
try:
|
|
510
|
+
console.print("─" * console.width, style="rgb(30,30,30)")
|
|
510
511
|
console.print() # Extra newline after user input to separate from LLM response
|
|
511
512
|
# Add user message
|
|
512
513
|
if TOOLS_ENABLED:
|
package/src/utils/settings.py
CHANGED
|
@@ -28,10 +28,14 @@ def left_align_headings(text: str) -> str:
|
|
|
28
28
|
@dataclass
|
|
29
29
|
class ServerSettings:
|
|
30
30
|
"""Local llama-server configuration."""
|
|
31
|
-
ngl_layers: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("ngl_layers",
|
|
31
|
+
ngl_layers: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("ngl_layers", 99))
|
|
32
32
|
ctx_size: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("ctx_size", 8192))
|
|
33
33
|
n_predict: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("n_predict", 8192))
|
|
34
34
|
rope_scale: float = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("rope_scale", 1.0))
|
|
35
|
+
threads: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("threads", 4))
|
|
36
|
+
batch_size: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("batch_size", 2048))
|
|
37
|
+
ubatch_size: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("ubatch_size", 512))
|
|
38
|
+
flash_attn: bool = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("flash_attn", True))
|
|
35
39
|
health_check_timeout_sec: int = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("health_check_timeout_sec", 120))
|
|
36
40
|
health_check_interval_sec: float = field(default_factory=lambda: _CONFIG.get("SERVER_SETTINGS", {}).get("health_check_interval_sec", 1.0))
|
|
37
41
|
|
|
@@ -104,6 +108,18 @@ class ContextSettings:
|
|
|
104
108
|
self.hard_limit_tokens = int(self.max_context_window * 0.9)
|
|
105
109
|
|
|
106
110
|
|
|
111
|
+
@dataclass
|
|
112
|
+
class PromptSettings:
|
|
113
|
+
"""Prompt variant selection."""
|
|
114
|
+
variant: str = field(default_factory=lambda: _CONFIG.get("PROMPT_SETTINGS", {}).get("variant", "micro"))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class DreamSettings:
|
|
119
|
+
"""Dream memory consolidation settings."""
|
|
120
|
+
enabled: bool = field(default_factory=lambda: _CONFIG.get("DREAM_SETTINGS", {}).get("enabled", True))
|
|
121
|
+
|
|
122
|
+
|
|
107
123
|
@dataclass
|
|
108
124
|
class ObsidianSettings:
|
|
109
125
|
"""Obsidian vault integration settings.
|
|
@@ -165,8 +181,9 @@ tool_settings = ToolSettings()
|
|
|
165
181
|
file_settings = FileSettings()
|
|
166
182
|
context_settings = ContextSettings()
|
|
167
183
|
sub_agent_settings = SubAgentSettings()
|
|
184
|
+
dream_settings = DreamSettings()
|
|
168
185
|
obsidian_settings = ObsidianSettings()
|
|
169
|
-
|
|
186
|
+
prompt_settings = PromptSettings()
|
|
170
187
|
# Tool execution constants
|
|
171
188
|
MAX_TOOL_CALLS = tool_settings.max_tool_calls
|
|
172
189
|
MAX_COMMAND_OUTPUT_LINES = tool_settings.max_command_output_lines
|
|
@@ -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
|