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.
Files changed (87) hide show
  1. package/README.md +19 -2
  2. package/config.yaml.example +13 -2
  3. package/package.json +3 -2
  4. package/prompts/main/ask_questions.md +31 -0
  5. package/prompts/main/batch_independent_calls.md +5 -0
  6. package/prompts/main/casual_interactions.md +11 -0
  7. package/prompts/main/code_references.md +8 -0
  8. package/prompts/main/communication_style.md +12 -0
  9. package/prompts/main/context_reliability.md +12 -0
  10. package/prompts/main/conversational_tool_calling.md +15 -0
  11. package/prompts/main/dream.md +50 -0
  12. package/prompts/main/editing_pattern.md +13 -0
  13. package/prompts/main/error_handling.md +6 -0
  14. package/prompts/main/exploration_pattern.md +21 -0
  15. package/prompts/main/intro.md +1 -0
  16. package/prompts/main/obsidian.md +16 -0
  17. package/prompts/main/obsidian_project.md +79 -0
  18. package/prompts/main/professional_objectivity.md +3 -0
  19. package/prompts/main/skills.md +3 -0
  20. package/prompts/main/targeted_searching.md +10 -0
  21. package/prompts/main/task_lists_pattern.md +8 -0
  22. package/prompts/main/temp_folder.md +9 -0
  23. package/prompts/main/think_before_acting.md +10 -0
  24. package/prompts/main/tone_and_style.md +4 -0
  25. package/prompts/main/tool_preferences.md +24 -0
  26. package/prompts/main/trust_subagent_context.md +21 -0
  27. package/prompts/main/when_to_use_sub_agent.md +7 -0
  28. package/prompts/micro/ask_questions.md +1 -0
  29. package/prompts/micro/batch_independent_calls.md +1 -0
  30. package/prompts/micro/casual_interactions.md +1 -0
  31. package/prompts/micro/code_references.md +1 -0
  32. package/prompts/micro/communication_style.md +1 -0
  33. package/prompts/micro/context_reliability.md +1 -0
  34. package/prompts/micro/conversational_tool_calling.md +1 -0
  35. package/prompts/micro/editing_pattern.md +1 -0
  36. package/prompts/micro/error_handling.md +1 -0
  37. package/prompts/micro/exploration_pattern.md +1 -0
  38. package/prompts/micro/intro.md +1 -0
  39. package/prompts/micro/obsidian.md +4 -0
  40. package/prompts/micro/obsidian_project.md +5 -0
  41. package/prompts/micro/professional_objectivity.md +1 -0
  42. package/prompts/micro/skills.md +1 -0
  43. package/prompts/micro/targeted_searching.md +1 -0
  44. package/prompts/micro/task_lists_pattern.md +1 -0
  45. package/prompts/micro/temp_folder.md +1 -0
  46. package/prompts/micro/think_before_acting.md +5 -0
  47. package/prompts/micro/tone_and_style.md +1 -0
  48. package/prompts/micro/tool_preferences.md +1 -0
  49. package/prompts/micro/trust_subagent_context.md +1 -0
  50. package/prompts/micro/when_to_use_sub_agent.md +1 -0
  51. package/src/core/agentic.py +134 -106
  52. package/src/core/chat_manager.py +60 -12
  53. package/src/core/config_manager.py +14 -1
  54. package/src/core/cron.py +57 -6
  55. package/src/core/memory.py +3 -90
  56. package/src/core/metadata.py +75 -0
  57. package/src/core/skills.py +463 -0
  58. package/src/core/sub_agent.py +93 -43
  59. package/src/core/tool_feedback.py +87 -76
  60. package/src/llm/client.py +7 -2
  61. package/src/llm/codex_provider.py +350 -0
  62. package/src/llm/config.py +74 -4
  63. package/src/llm/prompts.py +261 -502
  64. package/src/llm/providers.py +28 -7
  65. package/src/llm/token_tracker.py +32 -1
  66. package/src/tools/__init__.py +24 -85
  67. package/src/tools/create_file.py +1 -1
  68. package/src/tools/directory.py +1 -1
  69. package/src/tools/edit.py +13 -7
  70. package/src/tools/file_reader.py +1 -1
  71. package/src/tools/helpers/__init__.py +1 -7
  72. package/src/tools/helpers/base.py +65 -16
  73. package/src/tools/helpers/loader.py +2 -88
  74. package/src/tools/helpers/path_resolver.py +70 -13
  75. package/src/tools/helpers/plugin_manifest.py +99 -70
  76. package/src/tools/review_sub_agent.py +2 -1
  77. package/src/tools/rg_search.py +119 -35
  78. package/src/tools/search_plugins.py +140 -72
  79. package/src/tools/shell.py +3 -3
  80. package/src/ui/commands.py +470 -33
  81. package/src/ui/displays.py +27 -1
  82. package/src/ui/main.py +1 -4
  83. package/src/ui/tool_confirmation.py +16 -5
  84. package/src/utils/editor.py +88 -39
  85. package/src/utils/settings.py +25 -4
  86. package/src/utils/user_message_logger.py +120 -0
  87. package/src/utils/validation.py +10 -0
@@ -1,38 +1,41 @@
1
- """search_plugins core tool for on-demand plugin discovery.
1
+ """search_plugins core tool for on-demand capability discovery.
2
2
 
3
3
  This tool lets the LLM agent search for available plugin tools and
4
- activate them. Plugin schemas are not sent by default to avoid context
5
- bloat they are only included after activation.
4
+ stored skills, then explicitly activate plugins or load skills through
5
+ the same entrypoint. Plugin schemas are not sent by default to avoid
6
+ context bloat — they are only included after activation.
6
7
  """
7
8
 
8
- from typing import List, Optional
9
- from pathlib import Path
10
-
11
9
  from tools.helpers.base import tool, ToolRegistry, TERMINAL_NONE
12
10
 
11
+ HEADER_MATCHES = "Capability matches for: "
12
+ HEADER_ALL = "All available capabilities"
13
+
13
14
 
14
15
  @tool(
15
16
  name="search_plugins",
16
17
  description=(
17
- "Search for available plugin tools that can help with your task. "
18
- "Plugin tools are NOT in your available tools by default — use this "
19
- "to discover and activate them. Returns matching plugin names and "
20
- "descriptions. Once activated, the plugin's full schema will be "
21
- "available in your next response."
18
+ "Search for available plugin tools and saved skills that can help "
19
+ "with your task. Plugins are NOT in your available tools by default "
20
+ "— use this to discover and activate them. Skills can also be loaded "
21
+ "through this same tool by passing explicit capability names in 'load'. "
22
+ "Once a plugin is activated, its full schema will be available in your "
23
+ "next response."
22
24
  ),
23
25
  parameters={
24
26
  "type": "object",
25
27
  "properties": {
26
28
  "query": {
27
29
  "type": "string",
28
- "description": "Search query describing what you need (e.g., 'send email', 'query database', 'http request')"
30
+ "description": "Search query describing what you need (e.g., 'send email', 'query database', 'http request'). Omit to list all available plugins and skills."
29
31
  },
30
- "category": {
31
- "type": "string",
32
- "description": "Optional category filter (e.g., 'email', 'database', 'analysis')"
32
+ "load": {
33
+ "type": "array",
34
+ "items": {"type": "string"},
35
+ "description": "Optional list of exact capability names from the current search results to activate or load. Plugins are activated; skills are injected into the current chat."
33
36
  }
34
37
  },
35
- "required": ["query"]
38
+ "required": []
36
39
  },
37
40
  requires_approval=False,
38
41
  terminal_policy=TERMINAL_NONE,
@@ -41,69 +44,134 @@ from tools.helpers.base import tool, ToolRegistry, TERMINAL_NONE
41
44
  category="core"
42
45
  )
43
46
  def search_plugins(
44
- query: str,
45
- category: str = None,
47
+ query: str = "",
48
+ load: list[str] | None = None,
49
+ chat_manager=None,
46
50
  ) -> str:
47
- """Search the plugin manifest and activate matching plugins.
48
-
49
- Args:
50
- query: Search query describing the needed capability
51
- category: Optional category filter
51
+ """Search discoverable capabilities and optionally activate/load selected matches.
52
52
 
53
- Returns:
54
- Formatted result with activated plugin names and descriptions
55
- """
53
+ When using `load`, `query` must be provided so selections come from the current search results."""
54
+ from core.skills import SkillError, activate_skill, validate_skill_name
56
55
  from tools.helpers.plugin_manifest import plugin_manifest
57
56
 
58
- # Check if any core tool matches the query — early return
59
57
  core_tools = ToolRegistry.get_all(include_plugins=False)
60
- query_lower = query.lower()
61
- for ct in core_tools:
62
- if query_lower == ct.name.lower() or query_lower in ct.name.lower():
63
- return (
64
- f"exit_code=0\n"
65
- f"'{query}' matches a core tool that is already available: **{ct.name}**.\n"
66
- f"Description: {ct.description}"
67
- )
68
-
69
- # Search the plugin manifest
70
- matches = plugin_manifest.search(query, category=category, max_results=5)
58
+ core_tool_note = None
59
+ query = query.strip()
60
+
61
+ if load and not query:
62
+ return "\n".join([
63
+ "exit_code=1",
64
+ "Loading capabilities requires a query so selections come from the current search results.",
65
+ ])
66
+
67
+ # No query list everything
68
+ if not query:
69
+ matches = plugin_manifest.list_all_capabilities()
70
+ else:
71
+ query_lower = query.lower()
72
+ for ct in core_tools:
73
+ if query_lower == ct.name.lower():
74
+ core_tool_note = (
75
+ f"Core tool already available: {ct.name}\n"
76
+ f" {ct.description}"
77
+ )
78
+ break
79
+
80
+ matches = plugin_manifest.search_capabilities(query, max_results=10)
71
81
 
72
82
  if not matches:
73
- # No matches — suggest available categories
74
- categories = plugin_manifest.get_categories()
75
- if categories:
76
- cat_list = ", ".join(f"'{c}'" for c in categories)
77
- return (
78
- f"exit_code=0\n"
79
- f"No plugins found matching '{query}'.\n"
80
- f"Available plugin categories: {cat_list}\n"
81
- f"Total plugins in manifest: {plugin_manifest.plugin_count()}"
82
- )
83
- return (
84
- f"exit_code=0\n"
85
- f"No plugins found matching '{query}'. "
86
- f"No plugins are currently registered in the manifest."
87
- )
88
-
89
- # Activate matched plugins in the registry
90
- activated = []
91
- already_active = []
92
- for tool_def in matches:
93
- if ToolRegistry.is_plugin_active(tool_def.name):
94
- already_active.append(tool_def.name)
83
+ lines = ["exit_code=0"]
84
+ if core_tool_note:
85
+ lines.extend([core_tool_note, ""])
86
+ if query:
87
+ lines.append(f"No matches for: {query}")
95
88
  else:
96
- ToolRegistry.activate_plugin(tool_def)
97
- activated.append(tool_def.name)
98
-
99
- # Build result
100
- lines = [f"exit_code=0\nFound {len(matches)} plugin(s) matching '{query}':\n"]
101
-
102
- for tool_def in matches:
103
- status = "activated" if tool_def.name in activated else "already active"
104
- cat_part = f" [{tool_def.category}]" if tool_def.category else ""
105
- lines.append(f"- **{tool_def.name}**{cat_part} ({status}): {tool_def.description}")
106
- if tool_def.tags:
107
- lines.append(f" Tags: {', '.join(tool_def.tags)}")
89
+ lines.append("No plugins or skills available.")
90
+ return "\n".join(lines)
91
+
92
+ requested = [name for name in (load or []) if isinstance(name, str) and name.strip()]
93
+ requested_normalized = {name.strip().lower(): name.strip() for name in requested}
94
+ matched_by_name = {match.name.lower(): match for match in matches}
95
+
96
+ plugin_count = 0
97
+ skill_count = 0
98
+ loaded_plugins = []
99
+ loaded_skills = []
100
+ load_errors = []
101
+
102
+ for match in matches:
103
+ if match.kind == "plugin" and match.tool_def:
104
+ plugin_count += 1
105
+ if ToolRegistry.is_plugin_active(match.tool_def.name):
106
+ match.already_active = True
107
+ if match.name.lower() in requested_normalized and not match.already_active:
108
+ if ToolRegistry.activate_plugin(match.tool_def):
109
+ match.activated = True
110
+ loaded_plugins.append(match.name)
111
+ else:
112
+ load_errors.append(f"Plugin '{match.name}' is disabled. Enable it before loading.")
113
+ continue
114
+ skill_count += 1
115
+ if match.name.lower() in requested_normalized:
116
+ if chat_manager is None:
117
+ load_errors.append(f"Skill '{match.name}' cannot be loaded without an active chat.")
118
+ continue
119
+ try:
120
+ skill_name = validate_skill_name(match.name)
121
+ activate_skill(chat_manager, skill_name)
122
+ loaded_skills.append(skill_name)
123
+ except SkillError as exc:
124
+ load_errors.append(str(exc))
125
+
126
+ missing_requested = [
127
+ original_name
128
+ for normalized_name, original_name in requested_normalized.items()
129
+ if normalized_name not in matched_by_name
130
+ ]
131
+ for missing in missing_requested:
132
+ load_errors.append(f"Capability '{missing}' was not found in the current search results.")
133
+
134
+ lines = ["exit_code=0"]
135
+ if core_tool_note:
136
+ lines.extend([core_tool_note, ""])
137
+
138
+ if query:
139
+ lines.extend([
140
+ f"{HEADER_MATCHES}{query}",
141
+ f"Results: {len(matches)} total ({plugin_count} plugin, {skill_count} skill)",
142
+ "",
143
+ ])
144
+ else:
145
+ lines.extend([
146
+ HEADER_ALL,
147
+ f"Total: {len(matches)} ({plugin_count} plugin, {skill_count} skill)",
148
+ "",
149
+ ])
150
+
151
+ for match in matches:
152
+ if match.kind == "plugin":
153
+ status = "disabled" if ToolRegistry.is_disabled(match.name) else "activated" if match.activated else "active" if match.already_active else "available"
154
+ lines.append(f"- {match.name}")
155
+ lines.append(" type: plugin")
156
+ lines.append(f" status: {status}")
157
+ lines.append(f" summary: {match.description}")
158
+ if match.tags:
159
+ lines.append(f" tags: {', '.join(match.tags)}")
160
+ continue
161
+
162
+ lines.append(f"- {match.name}")
163
+ lines.append(" type: skill")
164
+ lines.append(f" summary: {match.description}")
165
+ if match.tags:
166
+ lines.append(f" tags: {', '.join(match.tags)}")
167
+
168
+ if requested:
169
+ lines.append("")
170
+ if loaded_plugins:
171
+ lines.append(f"Activated plugins: {', '.join(loaded_plugins)}")
172
+ if loaded_skills:
173
+ lines.append(f"Loaded skills: {', '.join(loaded_skills)}")
174
+ if load_errors:
175
+ lines.append(f"Load issues: {'; '.join(load_errors)}")
108
176
 
109
177
  return "\n".join(lines)
@@ -231,13 +231,13 @@ def run_shell_command(command, repo_root, rg_exe_path, console, debug_mode, giti
231
231
 
232
232
  @tool(
233
233
  name="execute_command",
234
- description="Execute shell commands for git, system tasks, file ops, network, and package management. Runs from repo root. Use for git, ps, systemctl, rm, mv, cp, mkdir, ping, curl, wget, ssh, pacman, pip, npm, apt. Disallowed: rg, cat, ls, grep, find, head, tail, sed, awk, sort, uniq, wc, echo, touch, get-content, type, get-childitem, dir, new-item, set-content, add-content, tee. Use native tools instead.",
234
+ description="Execute shell commands for git, system tasks, file ops, network, and package management. Runs from repo root. Multi-line shell commands are supported and preserved exactly, including heredocs. Use for git, ps, systemctl, rm, mv, cp, mkdir, ping, curl, wget, ssh, pacman, pip, npm, apt. Disallowed: rg, cat, ls, grep, find, head, tail, sed, awk, sort, uniq, wc, echo, touch, get-content, type, get-childitem, dir, new-item, set-content, add-content, tee. Use native tools instead.",
235
235
  parameters={
236
236
  "type": "object",
237
237
  "properties": {
238
238
  "command": {
239
239
  "type": "string",
240
- "description": "Command to execute"
240
+ "description": "Shell command to execute from the repo root. May be a single-line command or a multi-line shell script/heredoc; newlines are preserved exactly."
241
241
  },
242
242
  "reason": {
243
243
  "type": "string",
@@ -262,7 +262,7 @@ def execute_command(
262
262
  """Execute a shell command.
263
263
 
264
264
  Args:
265
- command: Command string to execute
265
+ command: Command string to execute. May contain newlines/heredocs; preserved exactly for shell execution.
266
266
  repo_root: Repository root directory (injected by context)
267
267
  rg_exe_path: Path to rg executable (injected by context)
268
268
  console: Rich console for output (injected by context)