bone-agent 1.3.2 → 1.4.0
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 +19 -2
- package/config.yaml.example +13 -2
- 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 +50 -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/skills.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/skills.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 +134 -106
- package/src/core/chat_manager.py +60 -12
- package/src/core/config_manager.py +14 -1
- package/src/core/cron.py +57 -6
- package/src/core/memory.py +3 -90
- package/src/core/metadata.py +75 -0
- package/src/core/skills.py +463 -0
- package/src/core/sub_agent.py +93 -43
- package/src/core/tool_feedback.py +87 -76
- package/src/llm/client.py +7 -2
- package/src/llm/codex_provider.py +350 -0
- package/src/llm/config.py +74 -4
- package/src/llm/prompts.py +261 -502
- package/src/llm/providers.py +28 -7
- package/src/llm/token_tracker.py +32 -1
- package/src/tools/__init__.py +24 -85
- package/src/tools/create_file.py +1 -1
- package/src/tools/directory.py +1 -1
- package/src/tools/edit.py +13 -7
- package/src/tools/file_reader.py +1 -1
- package/src/tools/helpers/__init__.py +1 -7
- package/src/tools/helpers/base.py +65 -16
- package/src/tools/helpers/loader.py +2 -88
- package/src/tools/helpers/path_resolver.py +70 -13
- package/src/tools/helpers/plugin_manifest.py +99 -70
- package/src/tools/review_sub_agent.py +2 -1
- package/src/tools/rg_search.py +119 -35
- package/src/tools/search_plugins.py +140 -72
- package/src/tools/shell.py +3 -3
- package/src/ui/commands.py +470 -33
- package/src/ui/displays.py +27 -1
- package/src/ui/main.py +1 -4
- package/src/ui/tool_confirmation.py +16 -5
- package/src/utils/editor.py +88 -39
- package/src/utils/settings.py +25 -4
- package/src/utils/user_message_logger.py +120 -0
- package/src/utils/validation.py +10 -0
package/src/llm/prompts.py
CHANGED
|
@@ -1,263 +1,20 @@
|
|
|
1
|
-
|
|
2
1
|
|
|
3
|
-
# Modular prompt composition for native function calling
|
|
4
|
-
# Base sections shared across all modes and sub-modes
|
|
5
|
-
|
|
6
|
-
BASE_SECTIONS = {
|
|
7
|
-
"intro": "You are a coding assistant that helps navigate codebases using native function calling.",
|
|
8
|
-
|
|
9
|
-
"tone_and_style": """## Tone and Style
|
|
10
|
-
- Be a intelligent, senior developer. Use first person (I, we).
|
|
11
|
-
- No emojis unless requested.
|
|
12
|
-
- Do not use uppercase text for emphasis unless the user explicitly instructs it.""",
|
|
13
|
-
|
|
14
|
-
"communication_style": """## Communication Style
|
|
15
|
-
|
|
16
|
-
**Important:** Default to concise explanations
|
|
17
|
-
|
|
18
|
-
- Show only changed code snippets when making edits via tools, never in explanations
|
|
19
|
-
- Use bullet points instead of prose when possible
|
|
20
|
-
- Target: 3-5 sentences max for explanations, 10-15 lines max for plans
|
|
21
|
-
- Explain the "why" and "what", skip the "how" unless requested
|
|
22
|
-
|
|
23
|
-
Examples:
|
|
24
|
-
❌ "I'll update the function by adding a parameter called `userId` and then modify the return statement to include..."
|
|
25
|
-
✓ "Add userId parameter to track user associations\"""",
|
|
26
|
-
|
|
27
|
-
"conversational_tool_calling": """## Conversational Tool Calling
|
|
28
|
-
|
|
29
|
-
Include explanatory text alongside tool calls to provide context.
|
|
30
|
-
|
|
31
|
-
**Share your thinking every 3-8 tool calls** - users need visibility into your reasoning during extended sequences.
|
|
32
|
-
|
|
33
|
-
**When to explain:**
|
|
34
|
-
- Starting exploration: explain initial strategy
|
|
35
|
-
- Making progress: summarize findings and next steps
|
|
36
|
-
- Getting stuck: explain why you're pivoting
|
|
37
|
-
- Redirecting: note when changing approach
|
|
38
|
-
|
|
39
|
-
**Skip for:** single obvious tool call at the start (e.g., "Reading config file"). Never skip for follow-up searches or sequences >1-2 calls.
|
|
40
|
-
|
|
41
|
-
Example: [Search: "auth handlers"] → [Read: auth.py] → [Thinking: "Found validate_token, checking handler"] → [Search: "token handler"] → [Read: handler.py] → [Answer]
|
|
42
|
-
""",
|
|
43
|
-
|
|
44
|
-
"professional_objectivity": """## Professional Objectivity
|
|
45
|
-
|
|
46
|
-
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without unnecessary superlatives, praise, or emotional validation. Apply rigorous standards to all ideas and disagree when necessary. Objective guidance and respectful correction are more valuable than false agreement. Investigate to find the truth rather than instinctively confirming beliefs.""",
|
|
47
|
-
|
|
48
|
-
"think_before_acting": """## Think Before Acting
|
|
49
|
-
|
|
50
|
-
**Decision Policy:**
|
|
51
|
-
1. What does the user need?
|
|
52
|
-
2. Is the answer available from visible context, prior tool results, or injected file contents?
|
|
53
|
-
3. If not, what's the minimum tool needed to fill the gap?
|
|
54
|
-
4. **Ambiguous?** If multiple valid approaches exist, use select_option to clarify before proceeding
|
|
55
|
-
5. Stop as soon as the answer is supported.
|
|
56
|
-
|
|
57
|
-
Use the smallest number of tool calls needed. Prefer one precise search over multiple broad searches.""",
|
|
58
|
-
|
|
59
|
-
"batch_independent_calls": """## Batch Independent Calls
|
|
60
|
-
|
|
61
|
-
**Important:** Batch independent tool calls to minimize tokens and latency.
|
|
62
|
-
|
|
63
|
-
Make independent calls in parallel (e.g., rg + read_file(file1) + read_file(file2)). If calls depend on previous results, run them sequentially. Never guess or use placeholders for dependent values.""",
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"trust_subagent_context": """## Trust Subagent Results
|
|
70
|
-
|
|
71
|
-
**Important:** When sub_agent returns results with '## INJECTED FILE CONTENTS', the files have already been read.
|
|
72
|
-
|
|
73
|
-
**You must:**
|
|
74
|
-
- Use the injected file contents directly
|
|
75
|
-
- Do not call `read_file()` for any file that appears in '## Injected File Contents'
|
|
76
|
-
- Do not re-read the same file with different line ranges
|
|
77
|
-
- Do not read "full file" when subagent already injected it
|
|
78
|
-
|
|
79
|
-
The injected code blocks contain the actual file content — not summaries.
|
|
80
|
-
|
|
81
|
-
Example:
|
|
82
|
-
- Subagent injects: '### src/auth.py (lines 45-78)'
|
|
83
|
-
- Use the injected content directly
|
|
84
|
-
- Do not call `read_file("src/auth.py", 45, 78)`
|
|
85
|
-
- Do not call `read_file("src/auth.py")` — don't read full file either
|
|
86
|
-
|
|
87
|
-
Only call `read_file()` for files not mentioned in the injected section.
|
|
88
|
-
|
|
89
|
-
Violating this instruction wastes tokens and shows you didn't read the subagent's work.""",
|
|
90
|
-
|
|
91
|
-
"context_reliability": """## Context Reliability
|
|
92
|
-
|
|
93
|
-
**Runtime Context Management:**
|
|
94
|
-
- Older tool results may be compacted, summarized, truncated, or absent from conversation history
|
|
95
|
-
- Only recent tool-assisted rounds may retain full verbatim outputs
|
|
96
|
-
- File contents from earlier reads may no longer be visible in current context
|
|
97
|
-
|
|
98
|
-
**Reacquisition Policy:**
|
|
99
|
-
- Use visible conversation context, prior tool results, and injected file contents first
|
|
100
|
-
- If needed facts are not visible in current context, reacquire only the missing fact with minimum tools
|
|
101
|
-
- After edits, treat earlier reads of that file as stale - re-read to verify final state
|
|
102
|
-
- Stop investigating once the answer is supported by available evidence""",
|
|
103
|
-
|
|
104
|
-
"code_references": """## Code References
|
|
105
|
-
|
|
106
|
-
When referencing specific functions or pieces of code include the pattern `file_path:line_number` to allow the user to easily navigate to the source code location.
|
|
107
|
-
|
|
108
|
-
<example>
|
|
109
|
-
user: Where are errors from the client handled?
|
|
110
|
-
assistant: Clients are marked as failed in the `connectToServer` function in src/services/process.ts:712.
|
|
111
|
-
</example>""",
|
|
112
|
-
|
|
113
|
-
"exploration_pattern": """## Exploration
|
|
114
|
-
|
|
115
|
-
1. If you know file path(s), start with `read_file` (use line ranges for files >500 lines)
|
|
116
|
-
2. Otherwise, start with targeted `rg` searches (specific keywords/functions)
|
|
117
|
-
3. Batch read all relevant files found
|
|
118
|
-
4. **If multiple exploration paths exist**, use select_option to confirm direction with user
|
|
119
|
-
5. Answer based on results
|
|
120
2
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
- `list_directory` shows line counts for each file (helps decide full vs partial reads)
|
|
128
|
-
- Files >500 lines should use `start_line` and `max_lines` parameters
|
|
129
|
-
|
|
130
|
-
**Track Previous Reads:**
|
|
131
|
-
- Check `start_line` and `lines_read` metadata from previous tool results
|
|
132
|
-
- Use this info to continue reading from where you left off
|
|
133
|
-
- Avoid re-reading lines you've already seen""",
|
|
134
|
-
|
|
135
|
-
"targeted_searching": """## Targeted Searching
|
|
136
|
-
|
|
137
|
-
**Avoid spam searches** - every rg call has latency:
|
|
138
|
-
1. **Reuse existing results** - before searching again, check if previous results already contain your answer
|
|
139
|
-
2. **Use files_with_matches first** - get file list, then read specific files
|
|
140
|
-
3. **One search often enough** - combine patterns with `|` before making multiple calls
|
|
141
|
-
4. **Specific > Generic** - search "def authenticate_user" not "auth" or "handle"
|
|
142
|
-
|
|
143
|
-
Good: single rg for pattern + read_file(file1) + read_file(file2)
|
|
144
|
-
Bad: rg → read → rg → read → rg → read (chaining sequential searches)""",
|
|
145
|
-
|
|
146
|
-
"editing_pattern": """## Editing
|
|
147
|
-
|
|
148
|
-
For every edit:
|
|
149
|
-
1. **Find exact text** to change (including whitespace/quotes)
|
|
150
|
-
2. **Copy exactly** for the `search` parameter
|
|
151
|
-
3. **Include context** to make search unique
|
|
152
|
-
4. **Never guess** — always verify search text matches
|
|
153
|
-
|
|
154
|
-
Tip: Read the file first to understand the context and find the exact text to edit.
|
|
155
|
-
|
|
156
|
-
If search appears multiple times, add more context. Copy character-for-character without reformatting.
|
|
157
|
-
|
|
158
|
-
**Before editing multiple files**: If there are multiple valid implementation approaches with different trade-offs, use select_option to clarify which approach the user prefers.""",
|
|
159
|
-
|
|
160
|
-
"task_lists_pattern": """## Task Lists
|
|
161
|
-
For multi-file edit sequences: `create_task_list` → `edit_file` → `complete_task(task_ids=[N,M,...])` (batch completions). Don't complete failed/rejected edits. Use `show_task_list` if lost. Don't paste task lists in responses; don't show after completing unless asked.
|
|
162
|
-
|
|
163
|
-
Single task: `complete_task(task_id=0)`
|
|
164
|
-
|
|
165
|
-
**Always include a `title`** when calling `create_task_list` — use a short phrase summarizing the workflow (e.g. 'Add pagination to user API').
|
|
166
|
-
|
|
167
|
-
**Before creating task lists**: If the edit approach involves significant trade-offs or architectural decisions, use select_option to confirm the approach with the user first.""",
|
|
168
|
-
|
|
169
|
-
"casual_interactions": """## Casual Interactions
|
|
170
|
-
|
|
171
|
-
Respond without tools for:
|
|
172
|
-
- Greetings, general explanations, conceptual questions
|
|
173
|
-
- Questions answerable from training data or codebase map
|
|
174
|
-
|
|
175
|
-
Not every question needs code exploration.""",
|
|
176
|
-
|
|
177
|
-
"ask_questions": """## Ask Questions
|
|
178
|
-
|
|
179
|
-
**Use select_option whenever you encounter:**
|
|
180
|
-
|
|
181
|
-
- **Ambiguity** - Multiple valid approaches and you're unsure which to prioritize
|
|
182
|
-
- **Preferences** - User-specific choices (naming conventions, frameworks, patterns)
|
|
183
|
-
- **Trade-offs** - Performance vs maintainability, simplicity vs flexibility, etc.
|
|
184
|
-
- **Scope decisions** - How deep to go, what to include vs exclude
|
|
185
|
-
- **Clarification** - Unclear requirements or conflicting constraints
|
|
186
|
-
- **Priority conflicts** - When optimization goals compete (speed, memory, readability)
|
|
187
|
-
- **Design choices** - Architecture patterns, data structures, algorithms
|
|
188
|
-
|
|
189
|
-
**When not to ask:**
|
|
190
|
-
- Trivial decisions that don't impact the outcome
|
|
191
|
-
- Questions answerable from visible context or training data
|
|
192
|
-
- Single obvious solution exists
|
|
193
|
-
- User already specified their preference
|
|
194
|
-
|
|
195
|
-
**Examples:**
|
|
196
|
-
- "Which logging framework do you prefer: (loguru, structlog, standard logging)?"
|
|
197
|
-
- "Should I optimize for memory usage or execution speed?"
|
|
198
|
-
- "Do you want a simple implementation or a more extensible architecture?"
|
|
199
|
-
- "Should I handle edge case X now or document it for later?"
|
|
200
|
-
|
|
201
|
-
**Pattern:**
|
|
202
|
-
1. Recognize a decision point with trade-offs
|
|
203
|
-
2. Use select_option to present 2-5 clear options
|
|
204
|
-
3. Include brief descriptions for each option
|
|
205
|
-
4. Proceed based on user selection
|
|
206
|
-
|
|
207
|
-
This works in any mode.""",
|
|
208
|
-
|
|
209
|
-
"tool_preferences": """## Tool Preferences
|
|
210
|
-
|
|
211
|
-
**Prefer native tools over execute_command:**
|
|
212
|
-
- Use `rg` tool (not `execute_command rg`) for code searches
|
|
213
|
-
- Use `read_file` (not `Get-Content`) for reading files
|
|
214
|
-
- Use `list_directory` (not `Get-ChildItem`) for listing directories
|
|
215
|
-
- Use `create_file` (not `New-Item`) for creating files
|
|
216
|
-
- Use `edit_file` (not `Set-Content`/`Add-Content`) for editing files
|
|
217
|
-
|
|
218
|
-
**Use execute_command for:**
|
|
219
|
-
- Git operations: `git clone`, `git pull`, `git push`, `git status`, etc.
|
|
220
|
-
- File operations: `rm`, `mv`, `cp`, `mkdir`, `rmdir`, `chmod`, etc.
|
|
221
|
-
- System tasks: package management (`pacman`, `pip`, `npm`), process management (`ps`, `kill`), service management (`systemctl`)
|
|
222
|
-
- Network tools: `ping`, `curl`, `wget`, `ssh`, `scp`
|
|
223
|
-
- Development: `make`, `cmake`, building projects, running tests
|
|
224
|
-
- Any other shell commands that don't overlap with native tools
|
|
225
|
-
|
|
226
|
-
**Do not use execute_command for:**
|
|
227
|
-
- Code search: use `rg` tool
|
|
228
|
-
- Reading files: use `read_file` tool
|
|
229
|
-
- Listing directories: use `list_directory` tool
|
|
230
|
-
- Creating files: use `create_file` tool
|
|
231
|
-
- Editing files: use `edit_file` tool
|
|
232
|
-
- python/python3 commands to edit/modify files (use native tools: create_file, edit_file)""",
|
|
233
|
-
|
|
234
|
-
"when_to_use_sub_agent": """## When to Use sub_agent
|
|
235
|
-
|
|
236
|
-
Use for broad multi-file exploration when the answer is not already available from visible context. This includes tracing flows, architecture questions, and pattern analysis requiring multiple search+read cycles.
|
|
237
|
-
|
|
238
|
-
Do not call sub_agent when one direct read_file or one targeted rg is sufficient for the answer.
|
|
239
|
-
|
|
240
|
-
**Alternative: Use select_option** when you need user input on decisions, preferences, or clarifications - it's faster and more direct than exploration for trade-off questions.""",
|
|
241
|
-
|
|
242
|
-
"error_handling": """## Error Handling
|
|
243
|
-
|
|
244
|
-
1. Try alternative approach (different terms, different file)
|
|
245
|
-
2. If stuck, report what you tried
|
|
246
|
-
3. Don't retry the same failed approach
|
|
247
|
-
4. **If the error indicates ambiguity in requirements**, use select_option to clarify with the user rather than guessing""",
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
"temp_folder": """## Temp Folder
|
|
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.
|
|
252
9
|
|
|
253
|
-
|
|
10
|
+
import logging
|
|
254
11
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from string import Template
|
|
258
15
|
|
|
259
|
-
|
|
260
|
-
|
|
16
|
+
# Root of the prompts directory (repo root / prompts)
|
|
17
|
+
_PROMPTS_DIR = Path(__file__).resolve().parents[2] / "prompts"
|
|
261
18
|
|
|
262
19
|
|
|
263
20
|
# Mode section for main agent
|
|
@@ -287,7 +44,7 @@ Show code only when using `edit_file`/`create_file` tools. Keep text explanation
|
|
|
287
44
|
SUB_AGENT_SECTIONS = {
|
|
288
45
|
"token_budget": """## Token Budget
|
|
289
46
|
|
|
290
|
-
You have a total budget of approximately
|
|
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.""",
|
|
291
48
|
|
|
292
49
|
"response_format": """# Response Format
|
|
293
50
|
|
|
@@ -388,31 +145,20 @@ SECTION_TOOL_DEPS = {
|
|
|
388
145
|
"editing_pattern": ["edit_file"],
|
|
389
146
|
"task_lists_pattern": ["create_task_list", "complete_task", "show_task_list", "edit_file"],
|
|
390
147
|
"temp_folder": ["create_file"],
|
|
391
|
-
"memory_system": ["edit_file"],
|
|
392
148
|
}
|
|
393
149
|
|
|
394
150
|
|
|
395
|
-
def _should_include_section(section_key: str) -> bool:
|
|
396
|
-
"""Check whether a prompt section should be included based on tool availability.
|
|
397
|
-
|
|
398
|
-
A section is skipped only when ALL of its dependent tools are disabled.
|
|
399
|
-
Uses lazy import to avoid circular dependency with tools module.
|
|
400
|
-
"""
|
|
401
|
-
deps = SECTION_TOOL_DEPS.get(section_key)
|
|
402
|
-
if not deps:
|
|
403
|
-
return True
|
|
404
|
-
from tools.helpers.base import ToolRegistry
|
|
405
|
-
return not all(ToolRegistry.is_disabled(t) for t in deps)
|
|
406
|
-
|
|
407
151
|
def _build_memory_section() -> str | None:
|
|
408
|
-
"""Build the memory
|
|
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.
|
|
409
157
|
|
|
410
|
-
Returns
|
|
411
|
-
and edit_file tool is enabled. Returns None otherwise.
|
|
158
|
+
Returns None if MemoryManager is not initialized or memory is disabled.
|
|
412
159
|
"""
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if ToolRegistry.is_disabled("edit_file"):
|
|
160
|
+
from llm.config import MEMORY_SETTINGS
|
|
161
|
+
if not MEMORY_SETTINGS.get("enabled", True):
|
|
416
162
|
return None
|
|
417
163
|
|
|
418
164
|
try:
|
|
@@ -420,25 +166,43 @@ def _build_memory_section() -> str | None:
|
|
|
420
166
|
manager = MemoryManager.get_instance()
|
|
421
167
|
if manager is None:
|
|
422
168
|
return None
|
|
423
|
-
|
|
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
|
|
424
186
|
except Exception:
|
|
425
187
|
return None
|
|
426
188
|
|
|
427
189
|
|
|
428
|
-
def _build_vault_section() -> str:
|
|
190
|
+
def _build_vault_section(variant: str = "main") -> str | None:
|
|
429
191
|
"""Build the Obsidian vault section for the system prompt.
|
|
430
192
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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.
|
|
434
199
|
"""
|
|
435
|
-
import logging
|
|
436
200
|
try:
|
|
437
201
|
from utils.settings import obsidian_settings
|
|
438
202
|
if not obsidian_settings.is_active():
|
|
439
203
|
return None
|
|
440
204
|
except Exception as e:
|
|
441
|
-
|
|
205
|
+
logger.debug("Obsidian not available: %s", e)
|
|
442
206
|
return None
|
|
443
207
|
|
|
444
208
|
try:
|
|
@@ -464,209 +228,231 @@ def _build_vault_section() -> str:
|
|
|
464
228
|
|
|
465
229
|
excluded = obsidian_settings.exclude_folders
|
|
466
230
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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()
|
|
472
240
|
|
|
241
|
+
# Substitute dynamic values using string.Template
|
|
473
242
|
if project_exists:
|
|
474
|
-
|
|
243
|
+
project_header = f"**Project folder:** `{project_folder}`"
|
|
475
244
|
else:
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
lines.extend([
|
|
479
|
-
"",
|
|
480
|
-
"**Path separation (CRITICAL):** Project folder is for **notes only**. "
|
|
481
|
-
"Code files use **relative paths** from repo root (e.g. `src/core/chat_manager.py`). "
|
|
482
|
-
"Never prepend vault/project paths to code paths.",
|
|
483
|
-
"",
|
|
484
|
-
"**Content routing (CRITICAL):** "
|
|
485
|
-
"ALL project notes (bugs, tasks, docs) MUST be created in the vault using `create_file` "
|
|
486
|
-
f"with absolute vault paths (e.g. `{project_folder}/Bugs/My bug title.md`). "
|
|
487
|
-
"Code changes (source, configs, tests) → relative repo paths. "
|
|
488
|
-
"Scratch/draft work → `.temp/` at repo root ONLY. "
|
|
489
|
-
"NEVER create vault notes in `.temp/`, the repo root, or any repo subdirectory.",
|
|
490
|
-
"",
|
|
491
|
-
"**Plan routing:** When asked to plan a feature or change, create a task note in "
|
|
492
|
-
f"`{project_folder}/Tasks/`. Do NOT create plan files in `.temp/` or the repo — "
|
|
493
|
-
"task notes ARE the plan records.",
|
|
494
|
-
"",
|
|
495
|
-
f"**Search:** `rg` scans both repo and vault (vault results show `[vault]` prefix). "
|
|
496
|
-
f"Excluded: {excluded}.",
|
|
497
|
-
"",
|
|
498
|
-
"**Rules:** `[[wiki-links]]` for cross-references, YAML frontmatter in all notes, "
|
|
499
|
-
"never touch `.obsidian/`, update `date_modified` on edits. "
|
|
500
|
-
"Code refs in notes: plain paths (not wiki-links).",
|
|
501
|
-
])
|
|
245
|
+
project_header = "**Project:** not initialized (run `/obsidian init` to create)"
|
|
502
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
|
|
503
255
|
if project_exists:
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
"`Tasks/Enhanced web search with full page content reading.md` (correct) vs "
|
|
511
|
-
"`Tasks/Enhanced Web Search/DuckDuckGo adapter.md` (wrong).",
|
|
512
|
-
"",
|
|
513
|
-
"**Title format:** `title: Short description in sentence case` — no quotes, "
|
|
514
|
-
"no type prefix (never `Bug: ...` or `Task N: ...`). The H1 heading must match "
|
|
515
|
-
"the title exactly.",
|
|
516
|
-
"",
|
|
517
|
-
"**Type field (exact values):** `type: bug | task | doc` — lowercase only.",
|
|
518
|
-
"",
|
|
519
|
-
"**Note schemas:** Every note MUST follow its type template exactly.",
|
|
520
|
-
"",
|
|
521
|
-
"- **Bug:** `Bugs/<title>.md`",
|
|
522
|
-
" Required FM: title, type (bug), status, priority, date_created, date_modified, tags.",
|
|
523
|
-
" Body sections: ## Related Files, ## Steps to Reproduce, ## Expected Behavior, ## Actual Behavior.",
|
|
524
|
-
" Optional body: ## Root Cause, ## Fix, ## Investigation Summary.",
|
|
525
|
-
"",
|
|
526
|
-
" Example:",
|
|
527
|
-
" ```",
|
|
528
|
-
" ---",
|
|
529
|
-
" title: First Letter Cut Off in Agent Response",
|
|
530
|
-
" type: bug",
|
|
531
|
-
" status: reported",
|
|
532
|
-
" priority: high",
|
|
533
|
-
" date_created: 2025-07-27",
|
|
534
|
-
" date_modified: 2025-07-27",
|
|
535
|
-
" tags: [bug, agent, rendering]",
|
|
536
|
-
" ---",
|
|
537
|
-
"",
|
|
538
|
-
" # First Letter Cut Off in Agent Response",
|
|
539
|
-
"",
|
|
540
|
-
" ## Related Files",
|
|
541
|
-
" - src/core/agentic.py:1154",
|
|
542
|
-
"",
|
|
543
|
-
" ## Steps to Reproduce",
|
|
544
|
-
" 1. Use agentic mode",
|
|
545
|
-
" 2. Get a response starting with characters in lstrip set",
|
|
546
|
-
"",
|
|
547
|
-
" ## Expected Behavior",
|
|
548
|
-
" Full first character/word is preserved.",
|
|
549
|
-
"",
|
|
550
|
-
" ## Actual Behavior",
|
|
551
|
-
" Leading characters are silently stripped.",
|
|
552
|
-
" ```",
|
|
553
|
-
"",
|
|
554
|
-
"- **Task:** `Tasks/<title>.md`",
|
|
555
|
-
" Required FM: title, type (task), status, priority, date_created, date_modified, tags.",
|
|
556
|
-
" Body sections: ## Related Files, ## Problem (or ## Scope / ## Description).",
|
|
557
|
-
"",
|
|
558
|
-
" Example:",
|
|
559
|
-
" ```",
|
|
560
|
-
" ---",
|
|
561
|
-
" title: Extract retry logic to src/core/retry.py",
|
|
562
|
-
" type: task",
|
|
563
|
-
" status: todo",
|
|
564
|
-
" priority: medium",
|
|
565
|
-
" date_created: 2025-07-10",
|
|
566
|
-
" date_modified: 2025-07-10",
|
|
567
|
-
" tags: [refactor, agentic]",
|
|
568
|
-
" ---",
|
|
569
|
-
"",
|
|
570
|
-
" # Extract retry logic to src/core/retry.py",
|
|
571
|
-
"",
|
|
572
|
-
" ## Related Files",
|
|
573
|
-
" - src/core/agentic.py:522-586",
|
|
574
|
-
" - src/core/retry.py (new)",
|
|
575
|
-
"",
|
|
576
|
-
" ## Scope",
|
|
577
|
-
" Move retry constants and functions from agentic.py into retry.py.",
|
|
578
|
-
" ```",
|
|
579
|
-
"",
|
|
580
|
-
"- **Doc:** `Docs/<title>.md`",
|
|
581
|
-
" Required FM: title, type (doc), date_created, date_modified, tags.",
|
|
582
|
-
" Optional FM: priority.",
|
|
583
|
-
" No required body sections — free-form markdown.",
|
|
584
|
-
"",
|
|
585
|
-
"**Common mistakes to avoid:**",
|
|
586
|
-
"- NEVER create `Bugs/`, `Tasks/` folders in the repo root",
|
|
587
|
-
"- NEVER put vault notes in `.temp/`",
|
|
588
|
-
"- NEVER use `# Bug:`, `# Task:` prefixes in H1 headings",
|
|
589
|
-
"- NEVER use quoted strings for title values in frontmatter",
|
|
590
|
-
"- NEVER nest folders (e.g. `Tasks/Some Feature/subtask.md`)",
|
|
591
|
-
"- NEVER use uppercase or mixed-case type values",
|
|
592
|
-
])
|
|
593
|
-
|
|
594
|
-
lines.extend([
|
|
595
|
-
"",
|
|
596
|
-
"**Archiving:** Terminal status (bug: fixed/verified, task: done) "
|
|
597
|
-
"→ move to `Done/` folder via `execute_command mv`. "
|
|
598
|
-
"User asks to sweep → `mv` each done note.",
|
|
599
|
-
])
|
|
600
|
-
|
|
601
|
-
return "\n".join(lines)
|
|
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
|
|
602
262
|
|
|
603
263
|
|
|
604
264
|
def _build_context_section() -> str:
|
|
605
|
-
"""Build a dynamic section with current date
|
|
265
|
+
"""Build a dynamic section with current date and location."""
|
|
606
266
|
from datetime import datetime
|
|
267
|
+
import os
|
|
607
268
|
|
|
608
269
|
now = datetime.now()
|
|
609
270
|
date_str = now.strftime("%A, %B %d, %Y")
|
|
610
|
-
time_str = now.strftime("%I:%M %p")
|
|
611
|
-
timezone = now.astimezone().tzinfo
|
|
612
271
|
|
|
613
272
|
return (
|
|
614
273
|
"## Current Context\n\n"
|
|
615
274
|
f"**Date:** {date_str}\n"
|
|
616
|
-
f"**
|
|
275
|
+
f"**Working directory:** {os.getcwd()}\n"
|
|
617
276
|
)
|
|
618
277
|
|
|
619
278
|
|
|
620
|
-
def
|
|
621
|
-
"""
|
|
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 ""
|
|
622
286
|
|
|
623
|
-
|
|
624
|
-
|
|
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.
|
|
625
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
|
+
|
|
626
333
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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),
|
|
650
385
|
]
|
|
651
|
-
sections = [BASE_SECTIONS[k] for k in _base_keys if _should_include_section(k)]
|
|
652
386
|
|
|
653
|
-
# Dynamic date/time/location context (inserted right after intro)
|
|
654
|
-
sections.insert(1, _build_context_section())
|
|
655
387
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if _memory_section:
|
|
659
|
-
sections.append(_memory_section)
|
|
388
|
+
def _sub_agent_sections(variant: str) -> list[tuple[str, callable]]:
|
|
389
|
+
"""Return (key, content_fn) pairs for the sub-agent prompt.
|
|
660
390
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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.
|
|
665
436
|
|
|
666
|
-
|
|
667
|
-
|
|
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.
|
|
668
441
|
|
|
669
|
-
|
|
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
|
|
670
456
|
|
|
671
457
|
|
|
672
458
|
def build_sub_agent_prompt(sub_agent_type: str = "research", soft_limit_tokens: int | None = None, hard_limit_tokens: int | None = None) -> str:
|
|
@@ -680,56 +466,29 @@ def build_sub_agent_prompt(sub_agent_type: str = "research", soft_limit_tokens:
|
|
|
680
466
|
Returns:
|
|
681
467
|
Complete system prompt string
|
|
682
468
|
"""
|
|
683
|
-
|
|
684
|
-
if
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
# Base section keys in display order — filtered by tool availability
|
|
690
|
-
_sub_base_keys = [
|
|
691
|
-
"intro",
|
|
692
|
-
"tone_and_style",
|
|
693
|
-
"communication_style",
|
|
694
|
-
"conversational_tool_calling",
|
|
695
|
-
"professional_objectivity",
|
|
696
|
-
"think_before_acting",
|
|
697
|
-
"batch_independent_calls",
|
|
698
|
-
"code_references",
|
|
699
|
-
"exploration_pattern",
|
|
700
|
-
"targeted_searching",
|
|
701
|
-
"casual_interactions",
|
|
702
|
-
"temp_folder",
|
|
703
|
-
]
|
|
704
|
-
sections = [BASE_SECTIONS[k] for k in _sub_base_keys if _should_include_section(k)]
|
|
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
|
+
)
|
|
705
475
|
|
|
706
|
-
|
|
707
|
-
sections.insert(1, _build_context_section())
|
|
476
|
+
result = _build_prompt_to_list(_sub_agent_sections(variant))
|
|
708
477
|
|
|
709
|
-
#
|
|
710
|
-
# to match the original prompt ordering (before the plugin-tier refactor).
|
|
711
|
-
response_format = SUB_AGENT_SECTIONS["response_format"]
|
|
712
|
-
inserted = False
|
|
713
|
-
result = []
|
|
714
|
-
for section in sections:
|
|
715
|
-
result.append(section)
|
|
716
|
-
if not inserted and section is BASE_SECTIONS.get("code_references"):
|
|
717
|
-
result.append(response_format)
|
|
718
|
-
inserted = True
|
|
719
|
-
if not inserted:
|
|
720
|
-
result.append(response_format)
|
|
721
|
-
|
|
722
|
-
# Insert token budget guidance before the mode section
|
|
478
|
+
# Append parameterized sections (always last)
|
|
723
479
|
if soft_limit_tokens is not None and hard_limit_tokens is not None:
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
+
)
|
|
727
485
|
)
|
|
728
|
-
result.append(token_budget)
|
|
729
|
-
|
|
730
|
-
result.append(mode_section)
|
|
731
|
-
return "\n\n".join(result)
|
|
732
486
|
|
|
487
|
+
if sub_agent_type == "review":
|
|
488
|
+
result.append(SUB_AGENT_SECTIONS["review_mode"])
|
|
489
|
+
else:
|
|
490
|
+
result.append(SUB_AGENT_SECTIONS["mode"])
|
|
733
491
|
|
|
492
|
+
return "\n\n".join(result)
|
|
734
493
|
|
|
735
494
|
|