kweaver-dolphin 0.1.0__py3-none-any.whl

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 (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,109 @@
1
+ """Configuration models for ResourceSkillkit.
2
+
3
+ This module contains configuration dataclasses for ResourceSkillkit settings.
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Optional
8
+ from pathlib import Path
9
+
10
+
11
+ # Default values as module constants to avoid duplication
12
+ _DEFAULT_DIRECTORIES = ["./skills"]
13
+ _DEFAULT_MAX_SKILL_SIZE_MB = 8
14
+ _DEFAULT_MAX_CONTENT_TOKENS = 8000
15
+ _DEFAULT_CACHE_TTL_SECONDS = 300
16
+ _DEFAULT_MAX_CACHE_SIZE = 100
17
+ _DEFAULT_MAX_SCAN_DEPTH = 6
18
+ _DEFAULT_ALLOWED_EXTENSIONS = [
19
+ ".py", ".sh", ".js", ".ts", ".md", ".txt", ".json", ".yaml", ".yml"
20
+ ]
21
+
22
+
23
+ @dataclass
24
+ class ResourceSkillConfig:
25
+ """Configuration for ResourceSkillkit.
26
+
27
+ Attributes:
28
+ enabled: Whether ResourceSkillkit is enabled
29
+ directories: List of directories to scan for skills (in priority order)
30
+ max_skill_size_mb: Maximum size of a single skill package in MB
31
+ max_content_tokens: Maximum tokens to return in a single load
32
+ cache_ttl_seconds: TTL for metadata cache in seconds
33
+ max_cache_size: Maximum number of items in cache
34
+ max_scan_depth: Maximum directory depth when scanning for skills
35
+ allowed_extensions: File extensions allowed for resource files
36
+ """
37
+
38
+ enabled: bool = True
39
+ directories: List[str] = field(default_factory=lambda: list(_DEFAULT_DIRECTORIES))
40
+ max_skill_size_mb: int = _DEFAULT_MAX_SKILL_SIZE_MB
41
+ max_content_tokens: int = _DEFAULT_MAX_CONTENT_TOKENS
42
+ cache_ttl_seconds: int = _DEFAULT_CACHE_TTL_SECONDS
43
+ max_cache_size: int = _DEFAULT_MAX_CACHE_SIZE
44
+ max_scan_depth: int = _DEFAULT_MAX_SCAN_DEPTH
45
+ allowed_extensions: List[str] = field(
46
+ default_factory=lambda: list(_DEFAULT_ALLOWED_EXTENSIONS)
47
+ )
48
+
49
+ @classmethod
50
+ def from_dict(cls, config_dict: dict) -> "ResourceSkillConfig":
51
+ """Create config from dictionary (e.g., from YAML config).
52
+
53
+ Args:
54
+ config_dict: Dictionary with configuration values
55
+
56
+ Returns:
57
+ ResourceSkillConfig instance
58
+ """
59
+ resource_config = config_dict.get("resource_skills", {})
60
+
61
+ limits = resource_config.get("limits", {})
62
+
63
+ return cls(
64
+ enabled=resource_config.get("enabled", True),
65
+ directories=resource_config.get("directories", _DEFAULT_DIRECTORIES),
66
+ max_skill_size_mb=limits.get("max_skill_size_mb", _DEFAULT_MAX_SKILL_SIZE_MB),
67
+ max_content_tokens=limits.get("max_content_tokens", _DEFAULT_MAX_CONTENT_TOKENS),
68
+ cache_ttl_seconds=resource_config.get("cache_ttl_seconds", _DEFAULT_CACHE_TTL_SECONDS),
69
+ max_cache_size=resource_config.get("max_cache_size", _DEFAULT_MAX_CACHE_SIZE),
70
+ max_scan_depth=resource_config.get("max_scan_depth", _DEFAULT_MAX_SCAN_DEPTH),
71
+ allowed_extensions=resource_config.get("allowed_extensions", _DEFAULT_ALLOWED_EXTENSIONS),
72
+ )
73
+
74
+ def get_resolved_directories(self, base_path: Optional[Path] = None) -> List[Path]:
75
+ """Resolve directory paths, expanding ~ and making absolute.
76
+
77
+ Args:
78
+ base_path: Optional base path for resolving relative paths
79
+
80
+ Returns:
81
+ List of resolved absolute Path objects
82
+ """
83
+ resolved = []
84
+ for dir_str in self.directories:
85
+ path = Path(dir_str).expanduser()
86
+ if not path.is_absolute():
87
+ if base_path:
88
+ path = base_path / path
89
+ else:
90
+ path = Path.cwd() / path
91
+ resolved.append(path.resolve())
92
+ return resolved
93
+
94
+ def to_dict(self) -> dict:
95
+ """Convert to dictionary representation.
96
+
97
+ Returns:
98
+ Dictionary with all configuration fields
99
+ """
100
+ return {
101
+ "enabled": self.enabled,
102
+ "directories": self.directories,
103
+ "max_skill_size_mb": self.max_skill_size_mb,
104
+ "max_content_tokens": self.max_content_tokens,
105
+ "cache_ttl_seconds": self.cache_ttl_seconds,
106
+ "max_cache_size": self.max_cache_size,
107
+ "max_scan_depth": self.max_scan_depth,
108
+ "allowed_extensions": self.allowed_extensions,
109
+ }
@@ -0,0 +1,127 @@
1
+ """Data models for ResourceSkillkit.
2
+
3
+ This module contains the SkillMeta and SkillContent dataclasses for
4
+ representing skill metadata (Level 1) and full content (Level 2).
5
+ """
6
+
7
+ from dataclasses import dataclass, field
8
+ from typing import List, Optional, Dict, Any
9
+
10
+
11
+ @dataclass
12
+ class SkillMeta:
13
+ """Level 1 metadata - lightweight skill information.
14
+
15
+ This represents the minimal metadata loaded for each skill,
16
+ used for system prompt injection (~100 tokens per skill).
17
+
18
+ Attributes:
19
+ name: Unique skill identifier
20
+ description: Brief description of what the skill does
21
+ base_path: Absolute path to the skill directory
22
+ version: Optional semantic version string
23
+ tags: Optional list of categorization tags
24
+ """
25
+
26
+ name: str
27
+ description: str
28
+ base_path: str
29
+ version: Optional[str] = None
30
+ tags: List[str] = field(default_factory=list)
31
+
32
+ def to_prompt_entry(self) -> str:
33
+ """Generate a markdown entry for system prompt injection.
34
+
35
+ Returns:
36
+ Formatted markdown string for this skill's metadata
37
+ """
38
+ return f"### {self.name}\n{self.description}"
39
+
40
+ def to_dict(self) -> Dict[str, Any]:
41
+ """Convert to dictionary representation.
42
+
43
+ Returns:
44
+ Dictionary with all metadata fields
45
+ """
46
+ return {
47
+ "name": self.name,
48
+ "description": self.description,
49
+ "base_path": self.base_path,
50
+ "version": self.version,
51
+ "tags": self.tags,
52
+ }
53
+
54
+
55
+ @dataclass
56
+ class SkillContent:
57
+ """Level 2 full content - complete SKILL.md content.
58
+
59
+ This represents the fully loaded skill content including
60
+ the YAML frontmatter and markdown body (~1500 tokens).
61
+
62
+ Attributes:
63
+ frontmatter: Parsed YAML frontmatter as dictionary
64
+ body: The markdown content after frontmatter
65
+ available_scripts: List of script file paths in scripts/ directory
66
+ available_references: List of reference file paths in references/ directory
67
+ """
68
+
69
+ frontmatter: Dict[str, Any]
70
+ body: str
71
+ available_scripts: List[str] = field(default_factory=list)
72
+ available_references: List[str] = field(default_factory=list)
73
+
74
+ def get_name(self) -> str:
75
+ """Get skill name from frontmatter.
76
+
77
+ Returns:
78
+ The skill name, or empty string if not found
79
+ """
80
+ return self.frontmatter.get("name", "")
81
+
82
+ def get_description(self) -> str:
83
+ """Get skill description from frontmatter.
84
+
85
+ Returns:
86
+ The skill description, or empty string if not found
87
+ """
88
+ return self.frontmatter.get("description", "")
89
+
90
+ def get_full_content(self) -> str:
91
+ """Get the complete content for Level 2 loading.
92
+
93
+ This includes the markdown body plus a list of available
94
+ resources that can be loaded via Level 3.
95
+
96
+ Returns:
97
+ Complete formatted content string
98
+ """
99
+ content_parts = [self.body]
100
+
101
+ if self.available_scripts or self.available_references:
102
+ content_parts.append("\n\n## Available Resources\n")
103
+
104
+ if self.available_scripts:
105
+ content_parts.append("\n**Scripts:**")
106
+ for script in self.available_scripts:
107
+ content_parts.append(f"\n- `{script}`")
108
+
109
+ if self.available_references:
110
+ content_parts.append("\n\n**References:**")
111
+ for ref in self.available_references:
112
+ content_parts.append(f"\n- `{ref}`")
113
+
114
+ return "".join(content_parts)
115
+
116
+ def to_dict(self) -> Dict[str, Any]:
117
+ """Convert to dictionary representation.
118
+
119
+ Returns:
120
+ Dictionary with all content fields
121
+ """
122
+ return {
123
+ "frontmatter": self.frontmatter,
124
+ "body": self.body,
125
+ "available_scripts": self.available_scripts,
126
+ "available_references": self.available_references,
127
+ }
@@ -0,0 +1,393 @@
1
+ """ResourceSkillkit - Claude Skill format support for Dolphin.
2
+
3
+ This module implements ResourceSkillkit, a Skillkit type that supports
4
+ resource/guidance skills in Claude Skill format (SKILL.md).
5
+
6
+ Key features:
7
+ - Progressive loading (Level 1/2/3)
8
+ - Prefix Cache optimization
9
+ - History bucket persistence
10
+ - Local-first design
11
+ """
12
+
13
+ from pathlib import Path
14
+ from typing import List, Optional, Dict, Any
15
+
16
+ from dolphin.core.common.constants import PIN_MARKER
17
+ from dolphin.core.skill.skill_function import SkillFunction
18
+ from dolphin.core.skill.skillkit import Skillkit
19
+ from dolphin.core.logging.logger import get_logger
20
+
21
+ from .models.skill_meta import SkillMeta, SkillContent
22
+ from .models.skill_config import ResourceSkillConfig
23
+ from .skill_loader import SkillLoader, truncate_content
24
+ from .skill_cache import SkillMetaCache, SkillContentCache
25
+ from .skill_validator import validate_skill_name
26
+
27
+ logger = get_logger("resource_skillkit")
28
+
29
+
30
+ class ResourceSkillkit(Skillkit):
31
+ """ResourceSkillkit - Support for Claude Skill format resources.
32
+
33
+ This Skillkit type provides support for resource/guidance skills
34
+ that teach LLMs how to solve complex problems. Unlike execution
35
+ skillkits (SQL, Python, MCP), ResourceSkillkit loads knowledge
36
+ resources in Claude Skill format (SKILL.md).
37
+
38
+ Progressive Loading:
39
+ - Level 1: Metadata (~100 tokens) - auto-injected to system prompt
40
+ - Level 2: Full SKILL.md content (~1500 tokens) - via tool call
41
+ - Level 3: Resource files (scripts/references) - on-demand
42
+
43
+ Prefix Cache Optimization:
44
+ - Level 1 metadata is fixed in system prompt
45
+ - Level 2 content enters _history bucket via tool response
46
+ - System prompt remains stable for cache hits
47
+
48
+ Example usage:
49
+ ```python
50
+ config = ResourceSkillConfig(
51
+ directories=["./skills", "~/.dolphin/skills"]
52
+ )
53
+ skillkit = ResourceSkillkit(config)
54
+ skillkit.initialize()
55
+
56
+ # Get metadata prompt for system injection
57
+ metadata_prompt = skillkit.getMetadataPrompt()
58
+
59
+ # Load full skill content (returns tool response)
60
+ content = skillkit.load_skill("data-pipeline")
61
+ ```
62
+ """
63
+
64
+ def __init__(self, config: Optional[ResourceSkillConfig] = None):
65
+ """Initialize ResourceSkillkit.
66
+
67
+ Args:
68
+ config: Optional configuration, uses defaults if not provided
69
+ """
70
+ super().__init__()
71
+ self.config = config or ResourceSkillConfig()
72
+ self.loader = SkillLoader(self.config)
73
+ self._meta_cache = SkillMetaCache(
74
+ ttl_seconds=self.config.cache_ttl_seconds,
75
+ max_size=self.config.max_cache_size,
76
+ )
77
+ self._content_cache = SkillContentCache(
78
+ ttl_seconds=self.config.cache_ttl_seconds * 2,
79
+ max_size=max(1, self.config.max_cache_size // 2),
80
+ )
81
+ self._initialized = False
82
+ self._skills_meta: Dict[str, SkillMeta] = {}
83
+ self._base_path: Optional[Path] = None
84
+
85
+ def setGlobalConfig(self, globalConfig) -> None:
86
+ super().setGlobalConfig(globalConfig)
87
+ resource_skills_cfg = getattr(globalConfig, "resource_skills", None)
88
+ if not isinstance(resource_skills_cfg, dict):
89
+ return
90
+
91
+ try:
92
+ new_config = ResourceSkillConfig.from_dict(
93
+ {"resource_skills": resource_skills_cfg}
94
+ )
95
+ except Exception as e:
96
+ logger.warning(f"Failed to parse resource_skills config: {e}")
97
+ return
98
+
99
+ self.config = new_config
100
+ self.loader = SkillLoader(self.config)
101
+ self._meta_cache = SkillMetaCache(
102
+ ttl_seconds=self.config.cache_ttl_seconds,
103
+ max_size=self.config.max_cache_size,
104
+ )
105
+ self._content_cache = SkillContentCache(
106
+ ttl_seconds=self.config.cache_ttl_seconds * 2,
107
+ max_size=max(1, self.config.max_cache_size // 2),
108
+ )
109
+ # Clear old state to avoid stale data
110
+ self._skills_meta.clear()
111
+ self._initialized = False
112
+
113
+ # Get base_dir from GlobalConfig to resolve relative paths
114
+ base_dir = getattr(globalConfig, "base_dir", None)
115
+ if base_dir:
116
+ from pathlib import Path
117
+ self._base_path = Path(base_dir)
118
+
119
+ def _ensure_initialized(self) -> None:
120
+ """Ensure the skillkit has scanned and loaded Level 1 metadata."""
121
+ if self._initialized:
122
+ return
123
+ self.initialize(self._base_path)
124
+
125
+ def getName(self) -> str:
126
+ """Get the name of this skillkit.
127
+
128
+ Returns:
129
+ The skillkit name
130
+ """
131
+ return "resource_skillkit"
132
+
133
+ def initialize(self, base_path: Optional[Path] = None) -> None:
134
+ """Initialize the skillkit by scanning for available skills.
135
+
136
+ This scans all configured directories and loads Level 1 metadata
137
+ for each valid skill found.
138
+
139
+ Args:
140
+ base_path: Optional base path for resolving relative directories
141
+ """
142
+ self._base_path = base_path
143
+ self._skills_meta.clear()
144
+ self._meta_cache.clear()
145
+
146
+ if not self.config.enabled:
147
+ logger.info("ResourceSkillkit is disabled")
148
+ self._initialized = True
149
+ return
150
+
151
+ # Scan directories and load metadata
152
+ metas = self.loader.scan_directories(base_path)
153
+
154
+ for meta in metas:
155
+ self._skills_meta[meta.name] = meta
156
+ self._meta_cache.set(meta.name, meta)
157
+
158
+ logger.info(f"Initialized ResourceSkillkit with {len(self._skills_meta)} skills")
159
+ self._initialized = True
160
+
161
+ def _createSkills(self) -> List[SkillFunction]:
162
+ """Create the list of skill functions provided by this skillkit.
163
+
164
+ Note: _list_resource_skills is NOT exposed as a tool because
165
+ Level 1 metadata is auto-injected into system prompt via
166
+ get_metadata_prompt(). LLM sees available skills upfront.
167
+
168
+ Returns:
169
+ List of SkillFunction objects for Level 2/3 loading
170
+ """
171
+ return [
172
+ SkillFunction(self._load_resource_skill),
173
+ SkillFunction(self._load_skill_resource),
174
+ ]
175
+
176
+ def get_metadata_prompt(self) -> str:
177
+ """Generate fixed metadata prompt for system injection (Level 1).
178
+
179
+ This generates the Level 1 metadata content that is automatically
180
+ injected into the system prompt. The content is stable and
181
+ sorted alphabetically to ensure Prefix Cache compatibility.
182
+
183
+ This overrides the base Skillkit.get_metadata_prompt() method.
184
+
185
+ Returns:
186
+ Markdown-formatted metadata prompt with available resource skills.
187
+ Always includes the section header, even when no skills are available.
188
+ """
189
+ self._ensure_initialized()
190
+
191
+ parts = ["## Available Resource Skills\n"]
192
+
193
+ if not self._skills_meta:
194
+ # Always include the section header to prevent hallucination
195
+ # when user's system prompt references this section
196
+ parts.append("\n_No resource skills are currently available._\n")
197
+ return "".join(parts)
198
+
199
+ # Sort by name for stable ordering (Prefix Cache)
200
+ for name in sorted(self._skills_meta.keys()):
201
+ meta = self._skills_meta[name]
202
+ parts.append(f"\n{meta.to_prompt_entry()}\n")
203
+
204
+ return "".join(parts)
205
+
206
+ def load_skill(self, name: str) -> str:
207
+ """Load Level 2 full content for a skill.
208
+
209
+ This is the internal method called by _load_resource_skill.
210
+ The returned content enters _history bucket as tool response.
211
+
212
+ Args:
213
+ name: The skill name to load
214
+
215
+ Returns:
216
+ Full SKILL.md content or error message
217
+ """
218
+ self._ensure_initialized()
219
+
220
+ # Validate name
221
+ is_valid, error = validate_skill_name(name)
222
+ if not is_valid:
223
+ return f"Error: {error}"
224
+
225
+ # Check if skill exists
226
+ if name not in self._skills_meta:
227
+ available = sorted(self._skills_meta.keys())
228
+ return (
229
+ f"Error: Skill '{name}' not found.\n"
230
+ f"Available skills: {', '.join(available) if available else 'none'}"
231
+ )
232
+
233
+ # Check content cache
234
+ cached_content = self._content_cache.get(name)
235
+ meta = self._skills_meta[name]
236
+ skill_dir = Path(meta.base_path)
237
+
238
+ # Build path info header to help Agent know where to execute commands
239
+ path_info = f"**Skill Root Directory:** `{skill_dir}`\n\n---\n\n"
240
+
241
+ if cached_content:
242
+ full_content = path_info + cached_content.get_full_content()
243
+ return truncate_content(full_content, self.config.max_content_tokens)
244
+
245
+ # Load from disk
246
+ content = self.loader.load_content(skill_dir)
247
+ if content is None:
248
+ return f"Error: Failed to load content for skill '{name}'"
249
+
250
+ # Cache the content
251
+ self._content_cache.set(name, content)
252
+
253
+ # Truncate if necessary - include path info at the beginning
254
+ full_content = path_info + content.get_full_content()
255
+ return truncate_content(full_content, self.config.max_content_tokens)
256
+
257
+ def load_resource(self, skill_name: str, resource_path: str) -> str:
258
+ """Load Level 3 resource file content.
259
+
260
+ This is the internal method called by _load_skill_resource.
261
+ The returned content is temporary (scratchpad).
262
+
263
+ Args:
264
+ skill_name: The skill name
265
+ resource_path: Relative path to resource file
266
+
267
+ Returns:
268
+ Resource file content or error message
269
+ """
270
+ self._ensure_initialized()
271
+
272
+ # Validate skill name
273
+ is_valid, error = validate_skill_name(skill_name)
274
+ if not is_valid:
275
+ return f"Error: {error}"
276
+
277
+ # Check if skill exists
278
+ if skill_name not in self._skills_meta:
279
+ available = sorted(self._skills_meta.keys())
280
+ return (
281
+ f"Error: Skill '{skill_name}' not found.\n"
282
+ f"Available skills: {', '.join(available) if available else 'none'}"
283
+ )
284
+
285
+ # Load resource
286
+ meta = self._skills_meta[skill_name]
287
+ skill_dir = Path(meta.base_path)
288
+
289
+ content, error = self.loader.load_resource(skill_dir, resource_path)
290
+ if error:
291
+ return f"Error: {error}"
292
+
293
+ # Format with file info header
294
+ return f"# {resource_path}\n\n```\n{content}\n```"
295
+
296
+ def clear_caches(self) -> None:
297
+ """Clear all internal caches.
298
+
299
+ This forces a reload from disk on next access.
300
+ """
301
+ self._meta_cache.clear()
302
+ self._content_cache.clear()
303
+
304
+ def get_available_skills(self) -> List[str]:
305
+ """Get list of available skill names.
306
+
307
+ Returns:
308
+ Sorted list of skill names
309
+ """
310
+ self._ensure_initialized()
311
+ return sorted(self._skills_meta.keys())
312
+
313
+ def get_skill_meta(self, name: str) -> Optional[SkillMeta]:
314
+ """Get metadata for a specific skill.
315
+
316
+ Args:
317
+ name: The skill name
318
+
319
+ Returns:
320
+ SkillMeta if found, None otherwise
321
+ """
322
+ return self._skills_meta.get(name)
323
+
324
+ # =====================================
325
+ # Tool Functions (exposed to LLM)
326
+ # =====================================
327
+
328
+ def _load_resource_skill(self, skill_name: str, **kwargs) -> str:
329
+ """Load the full instructions for a resource skill.
330
+
331
+ Loads the complete SKILL.md content for the specified skill.
332
+ The loaded content will be available in conversation history
333
+ for subsequent turns.
334
+
335
+ Args:
336
+ skill_name (str): Name of the skill to load
337
+
338
+ Returns:
339
+ str: Full skill content including instructions and available resources
340
+ """
341
+ content = self.load_skill(skill_name)
342
+ if content.startswith(PIN_MARKER):
343
+ return content
344
+ return f"{PIN_MARKER}\n{content}"
345
+
346
+ def _load_skill_resource(self, skill_name: str, resource_path: str, **kwargs) -> str:
347
+ """Load a specific resource file from a skill package.
348
+
349
+ Loads content from scripts/ or references/ directories within
350
+ a skill package. The resource path should be relative to the
351
+ skill directory (e.g., "scripts/etl.py").
352
+
353
+ Note: Level 3 resources are designed to be ephemeral (single-turn only).
354
+ Unlike Level 2 SKILL.md content, these resources do NOT use PIN_MARKER
355
+ and will not be persisted to history. They can be reloaded on-demand.
356
+
357
+ Args:
358
+ skill_name (str): Name of the skill
359
+ resource_path (str): Relative path to the resource file
360
+
361
+ Returns:
362
+ str: Resource file content
363
+ """
364
+ # Level 3: No PIN_MARKER - content goes to scratchpad, discarded after turn
365
+ return self.load_resource(skill_name, resource_path)
366
+
367
+ # =====================================
368
+ # Utility Methods
369
+ # =====================================
370
+
371
+ def refresh(self) -> int:
372
+ """Refresh the skill list by rescanning directories.
373
+
374
+ Returns:
375
+ Number of skills found
376
+ """
377
+ self.initialize(self._base_path)
378
+ return len(self._skills_meta)
379
+
380
+ def get_stats(self) -> Dict[str, Any]:
381
+ """Get statistics about the skillkit.
382
+
383
+ Returns:
384
+ Dictionary with statistics
385
+ """
386
+ return {
387
+ "enabled": self.config.enabled,
388
+ "initialized": self._initialized,
389
+ "skill_count": len(self._skills_meta),
390
+ "meta_cache": self._meta_cache.stats(),
391
+ "content_cache": self._content_cache.stats(),
392
+ "directories": self.config.directories,
393
+ }