bone-agent 1.4.0 → 2.0.1
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/bin/bone.js +39 -0
- package/package.json +25 -39
- package/LICENSE +0 -21
- package/README.md +0 -201
- package/bin/npm-wrapper.js +0 -235
- package/bin/rg +0 -0
- package/bin/rg.exe +0 -0
- package/config.yaml.example +0 -144
- package/prompts/main/ask_questions.md +0 -31
- package/prompts/main/batch_independent_calls.md +0 -5
- package/prompts/main/casual_interactions.md +0 -11
- package/prompts/main/code_references.md +0 -8
- package/prompts/main/communication_style.md +0 -12
- package/prompts/main/context_reliability.md +0 -12
- package/prompts/main/conversational_tool_calling.md +0 -15
- package/prompts/main/dream.md +0 -50
- package/prompts/main/editing_pattern.md +0 -13
- package/prompts/main/error_handling.md +0 -6
- package/prompts/main/exploration_pattern.md +0 -21
- package/prompts/main/intro.md +0 -1
- package/prompts/main/obsidian.md +0 -16
- package/prompts/main/obsidian_project.md +0 -79
- package/prompts/main/professional_objectivity.md +0 -3
- package/prompts/main/skills.md +0 -3
- package/prompts/main/targeted_searching.md +0 -10
- package/prompts/main/task_lists_pattern.md +0 -8
- package/prompts/main/temp_folder.md +0 -9
- package/prompts/main/think_before_acting.md +0 -10
- package/prompts/main/tone_and_style.md +0 -4
- package/prompts/main/tool_preferences.md +0 -24
- package/prompts/main/trust_subagent_context.md +0 -21
- package/prompts/main/when_to_use_sub_agent.md +0 -7
- package/prompts/micro/ask_questions.md +0 -1
- package/prompts/micro/batch_independent_calls.md +0 -1
- package/prompts/micro/casual_interactions.md +0 -1
- package/prompts/micro/code_references.md +0 -1
- package/prompts/micro/communication_style.md +0 -1
- package/prompts/micro/context_reliability.md +0 -1
- package/prompts/micro/conversational_tool_calling.md +0 -1
- package/prompts/micro/editing_pattern.md +0 -1
- package/prompts/micro/error_handling.md +0 -1
- package/prompts/micro/exploration_pattern.md +0 -1
- package/prompts/micro/intro.md +0 -1
- package/prompts/micro/obsidian.md +0 -4
- package/prompts/micro/obsidian_project.md +0 -5
- package/prompts/micro/professional_objectivity.md +0 -1
- package/prompts/micro/skills.md +0 -1
- package/prompts/micro/targeted_searching.md +0 -1
- package/prompts/micro/task_lists_pattern.md +0 -1
- package/prompts/micro/temp_folder.md +0 -1
- package/prompts/micro/think_before_acting.md +0 -5
- package/prompts/micro/tone_and_style.md +0 -1
- package/prompts/micro/tool_preferences.md +0 -1
- package/prompts/micro/trust_subagent_context.md +0 -1
- package/prompts/micro/when_to_use_sub_agent.md +0 -1
- package/requirements.txt +0 -9
- package/src/__init__.py +0 -11
- package/src/core/__init__.py +0 -1
- package/src/core/agentic.py +0 -1085
- package/src/core/chat_manager.py +0 -1577
- package/src/core/config_manager.py +0 -260
- package/src/core/cron.py +0 -578
- package/src/core/cron_allowlist.py +0 -118
- package/src/core/memory.py +0 -145
- package/src/core/metadata.py +0 -75
- package/src/core/retry.py +0 -71
- package/src/core/skills.py +0 -463
- package/src/core/sub_agent.py +0 -376
- package/src/core/tool_approval.py +0 -220
- package/src/core/tool_feedback.py +0 -789
- package/src/exceptions.py +0 -79
- package/src/llm/__init__.py +0 -1
- package/src/llm/client.py +0 -176
- package/src/llm/codex_provider.py +0 -350
- package/src/llm/config.py +0 -536
- package/src/llm/prompts.py +0 -494
- package/src/llm/providers.py +0 -438
- package/src/llm/streaming.py +0 -163
- package/src/llm/token_tracker.py +0 -399
- package/src/tools/__init__.py +0 -151
- package/src/tools/constants.py +0 -59
- package/src/tools/create_file.py +0 -136
- package/src/tools/directory.py +0 -389
- package/src/tools/edit.py +0 -549
- package/src/tools/file_reader.py +0 -322
- package/src/tools/helpers/__init__.py +0 -99
- package/src/tools/helpers/base.py +0 -599
- package/src/tools/helpers/converters.py +0 -44
- package/src/tools/helpers/file_helpers.py +0 -189
- package/src/tools/helpers/formatters.py +0 -411
- package/src/tools/helpers/loader.py +0 -145
- package/src/tools/helpers/parallel_executor.py +0 -231
- package/src/tools/helpers/path_resolver.py +0 -283
- package/src/tools/helpers/plugin_manifest.py +0 -185
- package/src/tools/obsidian.py +0 -96
- package/src/tools/review_sub_agent.py +0 -190
- package/src/tools/rg_search.py +0 -477
- package/src/tools/search_plugins.py +0 -177
- package/src/tools/select_option.py +0 -600
- package/src/tools/shell.py +0 -302
- package/src/tools/sub_agent.py +0 -139
- package/src/tools/task_list.py +0 -269
- package/src/tools/web_search.py +0 -61
- package/src/ui/__init__.py +0 -1
- package/src/ui/banner.py +0 -87
- package/src/ui/commands.py +0 -3131
- package/src/ui/displays.py +0 -239
- package/src/ui/loader.py +0 -284
- package/src/ui/main.py +0 -643
- package/src/ui/prompt_utils.py +0 -113
- package/src/ui/setting_selector.py +0 -590
- package/src/ui/setup_wizard.py +0 -294
- package/src/ui/sub_agent_panel.py +0 -234
- package/src/ui/tool_confirmation.py +0 -226
- package/src/utils/__init__.py +0 -1
- package/src/utils/citation_parser.py +0 -199
- package/src/utils/editor.py +0 -207
- package/src/utils/gitignore_filter.py +0 -149
- package/src/utils/logger.py +0 -254
- package/src/utils/paths.py +0 -30
- package/src/utils/result_parsers.py +0 -108
- package/src/utils/safe_commands.py +0 -243
- package/src/utils/settings.py +0 -195
- package/src/utils/user_message_logger.py +0 -120
- package/src/utils/validation.py +0 -201
- package/src/utils/web_search.py +0 -173
package/src/llm/prompts.py
DELETED
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# Modular prompt composition for native function calling
|
|
4
|
-
#
|
|
5
|
-
# All prompt sections are loaded from file-based variants under prompts/<variant>/.
|
|
6
|
-
# Each variant directory contains individual .md files for each section.
|
|
7
|
-
# Section ordering is defined programmatically in _main_sections() and
|
|
8
|
-
# _sub_agent_sections() — no manifest files needed.
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger(__name__)
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from string import Template
|
|
15
|
-
|
|
16
|
-
# Root of the prompts directory (repo root / prompts)
|
|
17
|
-
_PROMPTS_DIR = Path(__file__).resolve().parents[2] / "prompts"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# Mode section for main agent
|
|
21
|
-
|
|
22
|
-
MODE_SECTION = """## Current mode: Edit
|
|
23
|
-
|
|
24
|
-
**Important:** Explain changes conceptually, show code only in edit tools
|
|
25
|
-
|
|
26
|
-
Workflow:
|
|
27
|
-
1. Analyze request and identify files to modify
|
|
28
|
-
2. Generate a brief plan (what/where/why, no code)
|
|
29
|
-
3. **Check for trade-offs** - If multiple valid approaches exist, use select_option to clarify
|
|
30
|
-
4. Proceed with edits
|
|
31
|
-
|
|
32
|
-
When the user asks for a plan (e.g. "plan this out", "what's involved", "before you start"):
|
|
33
|
-
- Explore and understand requirements first
|
|
34
|
-
- Propose a structured plan with bullet points: what changes, where, and why
|
|
35
|
-
- Highlight trade-offs and ambiguities using select_option
|
|
36
|
-
- End with a summary of the proposed changes
|
|
37
|
-
- Ask: 'Do you approve this plan?' before proceeding with edits
|
|
38
|
-
|
|
39
|
-
Show code only when using `edit_file`/`create_file` tools. Keep text explanations concise."""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# Sub-agent specific sections (research-focused, read-only tools passed via function calling)
|
|
43
|
-
|
|
44
|
-
SUB_AGENT_SECTIONS = {
|
|
45
|
-
"token_budget": """## Token Budget
|
|
46
|
-
|
|
47
|
-
You have a total budget of approximately $hard_limit tokens for this task. When you reach $soft_limit tokens, you MUST immediately stop exploring and return your findings to the main agent. Do not continue reading files, searching, or making tool calls once you are near or past the soft limit. Wrap up your answer with citations and return it promptly.""",
|
|
48
|
-
|
|
49
|
-
"response_format": """# Response Format
|
|
50
|
-
|
|
51
|
-
When answering the main agent's query:
|
|
52
|
-
|
|
53
|
-
1. **Provide a clear summary** of your findings
|
|
54
|
-
2. **Cite only the most relevant files with precise line ranges** for code you've actually read
|
|
55
|
-
|
|
56
|
-
**Important:** Only cite files where you have actually read the content. The main agent will
|
|
57
|
-
inject the actual file contents based on your citations and will trust these injected contents
|
|
58
|
-
without re-reading them.
|
|
59
|
-
|
|
60
|
-
**Required:** You must use bracketed citation formats only. Unbracketed formats like `file:N`
|
|
61
|
-
will not be recognized and will be ignored.
|
|
62
|
-
|
|
63
|
-
Use these citation formats:
|
|
64
|
-
- `[path/to/file] (lines N-M)` - for a specific range you've fully read (preferred)
|
|
65
|
-
- `[path/to/file]:N-M` - bracketed range notation (preferred)
|
|
66
|
-
- `[path/to/file]:N` - bracketed single line notation (preferred)
|
|
67
|
-
- `[path/to/file] (full)` - only for small files or when you genuinely need the entire file
|
|
68
|
-
|
|
69
|
-
**Citation Guidelines - Be Selective:**
|
|
70
|
-
- Be precise with line numbers - cite only the specific ranges that matter
|
|
71
|
-
- Prioritize specific ranges (lines N-M) over full files
|
|
72
|
-
- Avoid citing large files with (full) - use specific ranges instead
|
|
73
|
-
- Omit boilerplate, tests, and utility code unless directly relevant
|
|
74
|
-
- The main agent can always request more context if needed
|
|
75
|
-
|
|
76
|
-
Example:
|
|
77
|
-
"The authentication flow starts in [src/core/auth.py] (lines 45-78) where tokens are validated,
|
|
78
|
-
then calls [src/core/session.py] (lines 112-145) for session management."
|
|
79
|
-
|
|
80
|
-
The main agent will automatically inject the actual file contents based on your citations,
|
|
81
|
-
so the main agent doesn't need to re-read files you've already explored.""",
|
|
82
|
-
|
|
83
|
-
"mode": """# Current mode: Research
|
|
84
|
-
|
|
85
|
-
You are a research sub-agent. Answer the specific question asked — do not explore the whole subsystem. Use read-only tools (rg, read_file, list_directory) to gather just enough information.
|
|
86
|
-
|
|
87
|
-
**Stop early:** Answer when you can address the query. The main agent can call you again for follow-up if needed. Prefer the most likely paths based on codebase structure.""",
|
|
88
|
-
|
|
89
|
-
"review_mode": """# Current mode: Code Review
|
|
90
|
-
|
|
91
|
-
You are a code review agent. Analyze the provided git diff and provide honest, useful feedback.
|
|
92
|
-
Your output goes directly to the user — write clean, readable markdown.
|
|
93
|
-
|
|
94
|
-
## Workflow
|
|
95
|
-
1. Parse file paths from diff headers (`+++ b/` or `--- a/`)
|
|
96
|
-
2. Use `read_file` on each changed file for surrounding context
|
|
97
|
-
3. Cross-reference related files when needed
|
|
98
|
-
4. Write your review
|
|
99
|
-
|
|
100
|
-
## Output Template
|
|
101
|
-
|
|
102
|
-
Follow this exact structure. Do not add extra sections or reorder.
|
|
103
|
-
|
|
104
|
-
### Summary
|
|
105
|
-
One paragraph (2-4 sentences). What changed, overall quality. If nothing noteworthy, say so.
|
|
106
|
-
|
|
107
|
-
### Issues
|
|
108
|
-
Group issues by severity under sub-headings. Only include levels that have findings.
|
|
109
|
-
|
|
110
|
-
#### Critical (N)
|
|
111
|
-
- `[path/to/file]:line` — short description
|
|
112
|
-
|
|
113
|
-
#### Warning (N)
|
|
114
|
-
- `[path/to/file]:line` — short description
|
|
115
|
-
|
|
116
|
-
#### Info (N)
|
|
117
|
-
- `[path/to/file]:line` — short description
|
|
118
|
-
|
|
119
|
-
Severity levels:
|
|
120
|
-
- **critical** — Blocking. Must fix before merge. Use sparingly.
|
|
121
|
-
- **warning** — Should fix, not blocking.
|
|
122
|
-
- **info** — Style, naming, nitpicks.
|
|
123
|
-
|
|
124
|
-
One bullet per issue. One line each. No paragraphs. Keep descriptions brief.
|
|
125
|
-
|
|
126
|
-
### Verdict
|
|
127
|
-
Always end with a verdict. One line: `APPROVE - explanation` or `REQUEST CHANGES - explanation`.
|
|
128
|
-
- `APPROVE` — no critical issues. Mention what looked good or minor nits.
|
|
129
|
-
- `REQUEST CHANGES` — critical issues found. Summarize what needs fixing.
|
|
130
|
-
|
|
131
|
-
## Anti-Fabrication Rule
|
|
132
|
-
Do not manufacture issues or inflate severity. If nothing is wrong, say so in the summary and skip those labels. An honest "No issues found" beats a fabricated nitpick. Use bracketed citations: `[path/to/file]:line_number`.""",
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
# Builder functions to compose prompts from sections
|
|
137
|
-
|
|
138
|
-
# Mapping of prompt section keys to the tool names they depend on.
|
|
139
|
-
# If ALL listed tools are disabled, the section is omitted from the prompt.
|
|
140
|
-
# Sections not listed have no tool dependency and are always included.
|
|
141
|
-
SECTION_TOOL_DEPS = {
|
|
142
|
-
"trust_subagent_context": ["sub_agent"],
|
|
143
|
-
"when_to_use_sub_agent": ["sub_agent"],
|
|
144
|
-
"ask_questions": ["select_option"],
|
|
145
|
-
"editing_pattern": ["edit_file"],
|
|
146
|
-
"task_lists_pattern": ["create_task_list", "complete_task", "show_task_list", "edit_file"],
|
|
147
|
-
"temp_folder": ["create_file"],
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _build_memory_section() -> str | None:
|
|
152
|
-
"""Build the read-only memory context section for the system prompt.
|
|
153
|
-
|
|
154
|
-
Injects live memory content blocks with capacity headers.
|
|
155
|
-
No writing instructions — memory files are read-only during conversations.
|
|
156
|
-
All writes happen through the dream cron job.
|
|
157
|
-
|
|
158
|
-
Returns None if MemoryManager is not initialized or memory is disabled.
|
|
159
|
-
"""
|
|
160
|
-
from llm.config import MEMORY_SETTINGS
|
|
161
|
-
if not MEMORY_SETTINGS.get("enabled", True):
|
|
162
|
-
return None
|
|
163
|
-
|
|
164
|
-
try:
|
|
165
|
-
from core.memory import MemoryManager
|
|
166
|
-
manager = MemoryManager.get_instance()
|
|
167
|
-
if manager is None:
|
|
168
|
-
return None
|
|
169
|
-
|
|
170
|
-
result = ""
|
|
171
|
-
|
|
172
|
-
# Append capacity headers and memory content if files have real content
|
|
173
|
-
user_content = manager.load_user_memory()
|
|
174
|
-
user_usage = manager.get_user_usage()
|
|
175
|
-
if manager._has_entries(user_content):
|
|
176
|
-
pct = user_usage["chars_used"] * 100 // user_usage["chars_limit"]
|
|
177
|
-
result += f"USER MEMORY [{pct}% — {user_usage['chars_used']}/{user_usage['chars_limit']} chars]\n{user_content.strip()}\n\n"
|
|
178
|
-
|
|
179
|
-
project_content = manager.load_project_memory()
|
|
180
|
-
project_usage = manager.get_project_usage()
|
|
181
|
-
if manager._has_entries(project_content):
|
|
182
|
-
pct = project_usage["chars_used"] * 100 // project_usage["chars_limit"]
|
|
183
|
-
result += f"PROJECT MEMORY [{pct}% — {project_usage['chars_used']}/{project_usage['chars_limit']} chars]\n{project_content.strip()}\n\n"
|
|
184
|
-
|
|
185
|
-
return result.strip() if result else None
|
|
186
|
-
except Exception:
|
|
187
|
-
return None
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def _build_vault_section(variant: str = "main") -> str | None:
|
|
191
|
-
"""Build the Obsidian vault section for the system prompt.
|
|
192
|
-
|
|
193
|
-
Loads obsidian.md from prompts/<variant>/ and substitutes dynamic values
|
|
194
|
-
(vault root, project folder, excluded folders) using string.Template.
|
|
195
|
-
If project exists, also loads and appends obsidian_project.md from the
|
|
196
|
-
same variant directory.
|
|
197
|
-
|
|
198
|
-
Returns None if vault is not active.
|
|
199
|
-
"""
|
|
200
|
-
try:
|
|
201
|
-
from utils.settings import obsidian_settings
|
|
202
|
-
if not obsidian_settings.is_active():
|
|
203
|
-
return None
|
|
204
|
-
except Exception as e:
|
|
205
|
-
logger.debug("Obsidian not available: %s", e)
|
|
206
|
-
return None
|
|
207
|
-
|
|
208
|
-
try:
|
|
209
|
-
from tools.obsidian import get_vault_session, init_session
|
|
210
|
-
session = get_vault_session()
|
|
211
|
-
# Initialize session on first prompt build if not yet available.
|
|
212
|
-
# Normally initialized by AgenticLoop.__init__, but the system prompt
|
|
213
|
-
# is built earlier (in ChatManager.__init__), causing an inconsistent
|
|
214
|
-
# vault section (missing note schemas) on fresh start.
|
|
215
|
-
if session is None:
|
|
216
|
-
session = init_session()
|
|
217
|
-
except Exception:
|
|
218
|
-
session = None
|
|
219
|
-
|
|
220
|
-
vault_root = str(session.vault_root) if session else "<not available>"
|
|
221
|
-
project_folder = str(session.project_folder) if session else "<not available>"
|
|
222
|
-
|
|
223
|
-
project_exists = (
|
|
224
|
-
session
|
|
225
|
-
and session.project_folder.is_dir()
|
|
226
|
-
and (session.project_folder / "Bugs").is_dir()
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
excluded = obsidian_settings.exclude_folders
|
|
230
|
-
|
|
231
|
-
# Load base obsidian template from variant directory
|
|
232
|
-
base_path = _PROMPTS_DIR / variant / "obsidian.md"
|
|
233
|
-
if not base_path.is_file():
|
|
234
|
-
logger.warning(
|
|
235
|
-
"Obsidian template not found at %s — vault section omitted", base_path
|
|
236
|
-
)
|
|
237
|
-
return None
|
|
238
|
-
|
|
239
|
-
base_content = base_path.read_text(encoding="utf-8").strip()
|
|
240
|
-
|
|
241
|
-
# Substitute dynamic values using string.Template
|
|
242
|
-
if project_exists:
|
|
243
|
-
project_header = f"**Project folder:** `{project_folder}`"
|
|
244
|
-
else:
|
|
245
|
-
project_header = "**Project:** not initialized (run `/obsidian init` to create)"
|
|
246
|
-
|
|
247
|
-
formatted = Template(base_content).safe_substitute(
|
|
248
|
-
vault_root=vault_root,
|
|
249
|
-
project_folder=project_folder,
|
|
250
|
-
project_header=project_header,
|
|
251
|
-
excluded=excluded,
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
# Append project-specific section if project exists
|
|
255
|
-
if project_exists:
|
|
256
|
-
project_path = _PROMPTS_DIR / variant / "obsidian_project.md"
|
|
257
|
-
if project_path.is_file():
|
|
258
|
-
project_content = project_path.read_text(encoding="utf-8").strip()
|
|
259
|
-
formatted = formatted + "\n\n" + project_content
|
|
260
|
-
|
|
261
|
-
return formatted
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
def _build_context_section() -> str:
|
|
265
|
-
"""Build a dynamic section with current date and location."""
|
|
266
|
-
from datetime import datetime
|
|
267
|
-
import os
|
|
268
|
-
|
|
269
|
-
now = datetime.now()
|
|
270
|
-
date_str = now.strftime("%A, %B %d, %Y")
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
"## Current Context\n\n"
|
|
274
|
-
f"**Date:** {date_str}\n"
|
|
275
|
-
f"**Working directory:** {os.getcwd()}\n"
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
def _static(variant: str, name: str) -> str:
|
|
280
|
-
"""Load a static .md section from prompts/<variant>/."""
|
|
281
|
-
path = _PROMPTS_DIR / variant / name
|
|
282
|
-
if path.is_file():
|
|
283
|
-
return path.read_text(encoding="utf-8").strip()
|
|
284
|
-
logger.warning("Section file not found: %s — prompt section '%s' omitted", path, name)
|
|
285
|
-
return ""
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
def _resolve_variant() -> str:
|
|
289
|
-
"""Resolve the prompt variant from settings, falling back to 'main'."""
|
|
290
|
-
try:
|
|
291
|
-
from utils.settings import prompt_settings
|
|
292
|
-
return prompt_settings.variant
|
|
293
|
-
except Exception:
|
|
294
|
-
return "main"
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def _should_include_section(section_key: str) -> bool:
|
|
298
|
-
"""Check whether a prompt section should be included based on tool availability.
|
|
299
|
-
|
|
300
|
-
A section is skipped only when ALL of its dependent tools are disabled.
|
|
301
|
-
Uses lazy import to avoid circular dependency with tools module.
|
|
302
|
-
"""
|
|
303
|
-
deps = SECTION_TOOL_DEPS.get(section_key)
|
|
304
|
-
if not deps:
|
|
305
|
-
return True
|
|
306
|
-
from tools.helpers.base import ToolRegistry
|
|
307
|
-
return not all(ToolRegistry.is_disabled(t) for t in deps)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
def _build_prompt_to_list(sections: list[tuple[str, callable]]) -> list[str]:
|
|
311
|
-
"""Build prompt as a list of section strings from (key, content_fn) pairs.
|
|
312
|
-
|
|
313
|
-
Skips sections that fail _should_include_section (tool deps)
|
|
314
|
-
or whose content_fn returns None/empty.
|
|
315
|
-
"""
|
|
316
|
-
result = []
|
|
317
|
-
for key, content_fn in sections:
|
|
318
|
-
if not _should_include_section(key):
|
|
319
|
-
continue
|
|
320
|
-
content = content_fn()
|
|
321
|
-
if content:
|
|
322
|
-
result.append(content)
|
|
323
|
-
return result
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
def _build_prompt(sections: list[tuple[str, callable]]) -> str:
|
|
327
|
-
"""Build prompt string from (key, content_fn) pairs.
|
|
328
|
-
|
|
329
|
-
Delegates to _build_prompt_to_list and joins the result.
|
|
330
|
-
"""
|
|
331
|
-
return "\n\n".join(_build_prompt_to_list(sections))
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def _variant_available(variant: str) -> bool:
|
|
335
|
-
"""Check if a variant directory exists.
|
|
336
|
-
|
|
337
|
-
Raises OSError if directory permissions prevent access.
|
|
338
|
-
"""
|
|
339
|
-
return (_PROMPTS_DIR / variant).is_dir()
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def _list_variants() -> list[str]:
|
|
343
|
-
"""List available prompt variants (directories under prompts/)."""
|
|
344
|
-
if not _PROMPTS_DIR.is_dir():
|
|
345
|
-
return []
|
|
346
|
-
return sorted(
|
|
347
|
-
d.name for d in _PROMPTS_DIR.iterdir()
|
|
348
|
-
if d.is_dir()
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
def _main_sections(variant: str) -> list[tuple[str, callable]]:
|
|
353
|
-
"""Return (key, content_fn) pairs for the main agent prompt.
|
|
354
|
-
|
|
355
|
-
Order in this list = order in the final prompt.
|
|
356
|
-
Static .md sections, dynamic builders, and hardcoded sections all
|
|
357
|
-
live here — single source of truth for ordering.
|
|
358
|
-
"""
|
|
359
|
-
return [
|
|
360
|
-
("intro", lambda: _static(variant, "intro.md")),
|
|
361
|
-
("context", _build_context_section),
|
|
362
|
-
("tone_and_style", lambda: _static(variant, "tone_and_style.md")),
|
|
363
|
-
("communication_style", lambda: _static(variant, "communication_style.md")),
|
|
364
|
-
("trust_subagent_context", lambda: _static(variant, "trust_subagent_context.md")),
|
|
365
|
-
("context_reliability", lambda: _static(variant, "context_reliability.md")),
|
|
366
|
-
("skills", lambda: _static(variant, "skills.md")),
|
|
367
|
-
("conversational_tool_calling", lambda: _static(variant, "conversational_tool_calling.md")),
|
|
368
|
-
("professional_objectivity", lambda: _static(variant, "professional_objectivity.md")),
|
|
369
|
-
("think_before_acting", lambda: _static(variant, "think_before_acting.md")),
|
|
370
|
-
("batch_independent_calls", lambda: _static(variant, "batch_independent_calls.md")),
|
|
371
|
-
("code_references", lambda: _static(variant, "code_references.md")),
|
|
372
|
-
("exploration_pattern", lambda: _static(variant, "exploration_pattern.md")),
|
|
373
|
-
("targeted_searching", lambda: _static(variant, "targeted_searching.md")),
|
|
374
|
-
("editing_pattern", lambda: _static(variant, "editing_pattern.md")),
|
|
375
|
-
("task_lists_pattern", lambda: _static(variant, "task_lists_pattern.md")),
|
|
376
|
-
("casual_interactions", lambda: _static(variant, "casual_interactions.md")),
|
|
377
|
-
("ask_questions", lambda: _static(variant, "ask_questions.md")),
|
|
378
|
-
("tool_preferences", lambda: _static(variant, "tool_preferences.md")),
|
|
379
|
-
("when_to_use_sub_agent", lambda: _static(variant, "when_to_use_sub_agent.md")),
|
|
380
|
-
("error_handling", lambda: _static(variant, "error_handling.md")),
|
|
381
|
-
("temp_folder", lambda: _static(variant, "temp_folder.md")),
|
|
382
|
-
("memory_system", _build_memory_section),
|
|
383
|
-
("obsidian", lambda: _build_vault_section(variant)),
|
|
384
|
-
("mode", lambda: MODE_SECTION),
|
|
385
|
-
]
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
def _sub_agent_sections(variant: str) -> list[tuple[str, callable]]:
|
|
389
|
-
"""Return (key, content_fn) pairs for the sub-agent prompt.
|
|
390
|
-
|
|
391
|
-
The micro variant has a smaller set of sections than main.
|
|
392
|
-
response_format is placed explicitly after code_references.
|
|
393
|
-
"""
|
|
394
|
-
# Base sections shared across all variants
|
|
395
|
-
base = [
|
|
396
|
-
("intro", lambda: _static(variant, "intro.md")),
|
|
397
|
-
("context", _build_context_section),
|
|
398
|
-
("tone_and_style", lambda: _static(variant, "tone_and_style.md")),
|
|
399
|
-
("communication_style", lambda: _static(variant, "communication_style.md")),
|
|
400
|
-
]
|
|
401
|
-
|
|
402
|
-
# Micro variant has a different section set (no conversational_tool_calling,
|
|
403
|
-
# no professional_objectivity, no think_before_acting, etc.)
|
|
404
|
-
if variant == "micro":
|
|
405
|
-
middle = [
|
|
406
|
-
("trust_subagent_context", lambda: _static(variant, "trust_subagent_context.md")),
|
|
407
|
-
("context_reliability", lambda: _static(variant, "context_reliability.md")),
|
|
408
|
-
("skills", lambda: _static(variant, "skills.md")),
|
|
409
|
-
("exploration_pattern", lambda: _static(variant, "exploration_pattern.md")),
|
|
410
|
-
("targeted_searching", lambda: _static(variant, "targeted_searching.md")),
|
|
411
|
-
("tool_preferences", lambda: _static(variant, "tool_preferences.md")),
|
|
412
|
-
]
|
|
413
|
-
else:
|
|
414
|
-
middle = [
|
|
415
|
-
("skills", lambda: _static(variant, "skills.md")),
|
|
416
|
-
("conversational_tool_calling", lambda: _static(variant, "conversational_tool_calling.md")),
|
|
417
|
-
("professional_objectivity", lambda: _static(variant, "professional_objectivity.md")),
|
|
418
|
-
("think_before_acting", lambda: _static(variant, "think_before_acting.md")),
|
|
419
|
-
("batch_independent_calls", lambda: _static(variant, "batch_independent_calls.md")),
|
|
420
|
-
("code_references", lambda: _static(variant, "code_references.md")),
|
|
421
|
-
("response_format", lambda: SUB_AGENT_SECTIONS["response_format"]),
|
|
422
|
-
("exploration_pattern", lambda: _static(variant, "exploration_pattern.md")),
|
|
423
|
-
("targeted_searching", lambda: _static(variant, "targeted_searching.md")),
|
|
424
|
-
("casual_interactions", lambda: _static(variant, "casual_interactions.md")),
|
|
425
|
-
("temp_folder", lambda: _static(variant, "temp_folder.md")),
|
|
426
|
-
]
|
|
427
|
-
|
|
428
|
-
return base + middle
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def build_system_prompt(variant: str | None = None, active_skills_section: str = "") -> str:
|
|
432
|
-
"""Build system prompt for main agent.
|
|
433
|
-
|
|
434
|
-
Loads section content from prompts/<variant>/. Order is defined by
|
|
435
|
-
_main_sections(). Raises FileNotFoundError if variant directory is missing.
|
|
436
|
-
|
|
437
|
-
Args:
|
|
438
|
-
variant: Variant name (e.g. 'main', 'micro').
|
|
439
|
-
If None, reads from settings.
|
|
440
|
-
active_skills_section: Optional rendered active-skills block to append.
|
|
441
|
-
|
|
442
|
-
Returns:
|
|
443
|
-
Complete system prompt string
|
|
444
|
-
"""
|
|
445
|
-
if variant is None:
|
|
446
|
-
variant = _resolve_variant()
|
|
447
|
-
if not _variant_available(variant):
|
|
448
|
-
raise FileNotFoundError(
|
|
449
|
-
f"Prompt variant '{variant}' not found: "
|
|
450
|
-
f"{_PROMPTS_DIR / variant} does not exist"
|
|
451
|
-
)
|
|
452
|
-
result = _build_prompt(_main_sections(variant))
|
|
453
|
-
if active_skills_section.strip():
|
|
454
|
-
result += "\n\n" + active_skills_section.strip()
|
|
455
|
-
return result
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
def build_sub_agent_prompt(sub_agent_type: str = "research", soft_limit_tokens: int | None = None, hard_limit_tokens: int | None = None) -> str:
|
|
459
|
-
"""Build prompt for sub-agent (research or review, read-only).
|
|
460
|
-
|
|
461
|
-
Args:
|
|
462
|
-
sub_agent_type: Type of sub-agent ('research' or 'review').
|
|
463
|
-
soft_limit_tokens: Soft token limit to display in prompt.
|
|
464
|
-
hard_limit_tokens: Hard token limit to display in prompt.
|
|
465
|
-
|
|
466
|
-
Returns:
|
|
467
|
-
Complete system prompt string
|
|
468
|
-
"""
|
|
469
|
-
variant = _resolve_variant()
|
|
470
|
-
if not _variant_available(variant):
|
|
471
|
-
raise FileNotFoundError(
|
|
472
|
-
f"Sub-agent prompt variant '{variant}' not found: "
|
|
473
|
-
f"{_PROMPTS_DIR / variant} does not exist"
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
result = _build_prompt_to_list(_sub_agent_sections(variant))
|
|
477
|
-
|
|
478
|
-
# Append parameterized sections (always last)
|
|
479
|
-
if soft_limit_tokens is not None and hard_limit_tokens is not None:
|
|
480
|
-
result.append(
|
|
481
|
-
Template(SUB_AGENT_SECTIONS["token_budget"]).safe_substitute(
|
|
482
|
-
soft_limit=f"{soft_limit_tokens:,}",
|
|
483
|
-
hard_limit=f"{hard_limit_tokens:,}",
|
|
484
|
-
)
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
if sub_agent_type == "review":
|
|
488
|
-
result.append(SUB_AGENT_SECTIONS["review_mode"])
|
|
489
|
-
else:
|
|
490
|
-
result.append(SUB_AGENT_SECTIONS["mode"])
|
|
491
|
-
|
|
492
|
-
return "\n\n".join(result)
|
|
493
|
-
|
|
494
|
-
|