skimpyclaw 0.1.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 (219) hide show
  1. package/README.md +230 -0
  2. package/dist/__tests__/agent.test.d.ts +1 -0
  3. package/dist/__tests__/agent.test.js +131 -0
  4. package/dist/__tests__/api.test.d.ts +1 -0
  5. package/dist/__tests__/api.test.js +1227 -0
  6. package/dist/__tests__/audit.test.d.ts +1 -0
  7. package/dist/__tests__/audit.test.js +122 -0
  8. package/dist/__tests__/cache.test.d.ts +1 -0
  9. package/dist/__tests__/cache.test.js +65 -0
  10. package/dist/__tests__/channels.test.d.ts +1 -0
  11. package/dist/__tests__/channels.test.js +85 -0
  12. package/dist/__tests__/cli.integration.test.d.ts +1 -0
  13. package/dist/__tests__/cli.integration.test.js +16 -0
  14. package/dist/__tests__/cli.test.d.ts +1 -0
  15. package/dist/__tests__/cli.test.js +230 -0
  16. package/dist/__tests__/code-agents-executor.test.d.ts +1 -0
  17. package/dist/__tests__/code-agents-executor.test.js +75 -0
  18. package/dist/__tests__/code-agents-orchestrator.test.d.ts +1 -0
  19. package/dist/__tests__/code-agents-orchestrator.test.js +149 -0
  20. package/dist/__tests__/code-agents-parser.test.d.ts +1 -0
  21. package/dist/__tests__/code-agents-parser.test.js +39 -0
  22. package/dist/__tests__/code-agents-utils.test.d.ts +1 -0
  23. package/dist/__tests__/code-agents-utils.test.js +41 -0
  24. package/dist/__tests__/config.test.d.ts +1 -0
  25. package/dist/__tests__/config.test.js +46 -0
  26. package/dist/__tests__/cron.test.d.ts +1 -0
  27. package/dist/__tests__/cron.test.js +66 -0
  28. package/dist/__tests__/dashboard-mode.test.d.ts +1 -0
  29. package/dist/__tests__/dashboard-mode.test.js +145 -0
  30. package/dist/__tests__/dashboard.test.d.ts +1 -0
  31. package/dist/__tests__/dashboard.test.js +43 -0
  32. package/dist/__tests__/doctor.formatters.test.d.ts +1 -0
  33. package/dist/__tests__/doctor.formatters.test.js +65 -0
  34. package/dist/__tests__/doctor.index.test.d.ts +1 -0
  35. package/dist/__tests__/doctor.index.test.js +48 -0
  36. package/dist/__tests__/doctor.runner.test.d.ts +1 -0
  37. package/dist/__tests__/doctor.runner.test.js +204 -0
  38. package/dist/__tests__/exec-approval.test.d.ts +1 -0
  39. package/dist/__tests__/exec-approval.test.js +323 -0
  40. package/dist/__tests__/file-lock.test.d.ts +1 -0
  41. package/dist/__tests__/file-lock.test.js +92 -0
  42. package/dist/__tests__/langfuse.test.d.ts +1 -0
  43. package/dist/__tests__/langfuse.test.js +40 -0
  44. package/dist/__tests__/model-selection.test.d.ts +1 -0
  45. package/dist/__tests__/model-selection.test.js +62 -0
  46. package/dist/__tests__/orchestrator.test.d.ts +1 -0
  47. package/dist/__tests__/orchestrator.test.js +425 -0
  48. package/dist/__tests__/providers-init.test.d.ts +1 -0
  49. package/dist/__tests__/providers-init.test.js +32 -0
  50. package/dist/__tests__/providers-routing.test.d.ts +1 -0
  51. package/dist/__tests__/providers-routing.test.js +25 -0
  52. package/dist/__tests__/providers-utils.test.d.ts +1 -0
  53. package/dist/__tests__/providers-utils.test.js +54 -0
  54. package/dist/__tests__/security.test.d.ts +1 -0
  55. package/dist/__tests__/security.test.js +22 -0
  56. package/dist/__tests__/sessions.test.d.ts +1 -0
  57. package/dist/__tests__/sessions.test.js +147 -0
  58. package/dist/__tests__/setup.test.d.ts +1 -0
  59. package/dist/__tests__/setup.test.js +114 -0
  60. package/dist/__tests__/skills.test.d.ts +1 -0
  61. package/dist/__tests__/skills.test.js +333 -0
  62. package/dist/__tests__/subagent.test.d.ts +1 -0
  63. package/dist/__tests__/subagent.test.js +240 -0
  64. package/dist/__tests__/telegram-utils.test.d.ts +1 -0
  65. package/dist/__tests__/telegram-utils.test.js +22 -0
  66. package/dist/__tests__/telegram.test.d.ts +1 -0
  67. package/dist/__tests__/telegram.test.js +42 -0
  68. package/dist/__tests__/token-efficiency.test.d.ts +1 -0
  69. package/dist/__tests__/token-efficiency.test.js +38 -0
  70. package/dist/__tests__/tool-guard.test.d.ts +1 -0
  71. package/dist/__tests__/tool-guard.test.js +105 -0
  72. package/dist/__tests__/tools.test.d.ts +1 -0
  73. package/dist/__tests__/tools.test.js +589 -0
  74. package/dist/__tests__/usage.test.d.ts +1 -0
  75. package/dist/__tests__/usage.test.js +197 -0
  76. package/dist/__tests__/voice.test.d.ts +1 -0
  77. package/dist/__tests__/voice.test.js +214 -0
  78. package/dist/agent.d.ts +24 -0
  79. package/dist/agent.js +269 -0
  80. package/dist/api.d.ts +3 -0
  81. package/dist/api.js +943 -0
  82. package/dist/audit.d.ts +26 -0
  83. package/dist/audit.js +121 -0
  84. package/dist/cache.d.ts +8 -0
  85. package/dist/cache.js +24 -0
  86. package/dist/channels/telegram/handlers.d.ts +41 -0
  87. package/dist/channels/telegram/handlers.js +498 -0
  88. package/dist/channels/telegram/index.d.ts +14 -0
  89. package/dist/channels/telegram/index.js +326 -0
  90. package/dist/channels/telegram/types.d.ts +26 -0
  91. package/dist/channels/telegram/types.js +31 -0
  92. package/dist/channels/telegram/utils.d.ts +25 -0
  93. package/dist/channels/telegram/utils.js +256 -0
  94. package/dist/channels.d.ts +11 -0
  95. package/dist/channels.js +118 -0
  96. package/dist/cli.d.ts +5 -0
  97. package/dist/cli.js +768 -0
  98. package/dist/code-agents/executor.d.ts +5 -0
  99. package/dist/code-agents/executor.js +463 -0
  100. package/dist/code-agents/index.d.ts +22 -0
  101. package/dist/code-agents/index.js +199 -0
  102. package/dist/code-agents/orchestrator.d.ts +23 -0
  103. package/dist/code-agents/orchestrator.js +403 -0
  104. package/dist/code-agents/parser.d.ts +21 -0
  105. package/dist/code-agents/parser.js +197 -0
  106. package/dist/code-agents/registry.d.ts +27 -0
  107. package/dist/code-agents/registry.js +147 -0
  108. package/dist/code-agents/types.d.ts +66 -0
  109. package/dist/code-agents/types.js +4 -0
  110. package/dist/code-agents/utils.d.ts +36 -0
  111. package/dist/code-agents/utils.js +236 -0
  112. package/dist/config.d.ts +19 -0
  113. package/dist/config.js +123 -0
  114. package/dist/cron.d.ts +49 -0
  115. package/dist/cron.js +400 -0
  116. package/dist/dashboard/assets/index-CZJCvMSN.js +65 -0
  117. package/dist/dashboard/assets/index-EAg6lqF5.css +1 -0
  118. package/dist/dashboard/favicon.svg +3 -0
  119. package/dist/dashboard/index.html +21 -0
  120. package/dist/dashboard-frontend.d.ts +7 -0
  121. package/dist/dashboard-frontend.js +86 -0
  122. package/dist/dashboard.d.ts +8 -0
  123. package/dist/dashboard.js +4071 -0
  124. package/dist/digests.d.ts +36 -0
  125. package/dist/digests.js +338 -0
  126. package/dist/discord.d.ts +8 -0
  127. package/dist/discord.js +828 -0
  128. package/dist/doctor/checks.d.ts +18 -0
  129. package/dist/doctor/checks.js +368 -0
  130. package/dist/doctor/formatters.d.ts +3 -0
  131. package/dist/doctor/formatters.js +44 -0
  132. package/dist/doctor/index.d.ts +8 -0
  133. package/dist/doctor/index.js +7 -0
  134. package/dist/doctor/runner.d.ts +3 -0
  135. package/dist/doctor/runner.js +109 -0
  136. package/dist/doctor/types.d.ts +20 -0
  137. package/dist/doctor/types.js +1 -0
  138. package/dist/exec-approval.d.ts +101 -0
  139. package/dist/exec-approval.js +432 -0
  140. package/dist/file-lock.d.ts +34 -0
  141. package/dist/file-lock.js +81 -0
  142. package/dist/gateway.d.ts +8 -0
  143. package/dist/gateway.js +114 -0
  144. package/dist/heartbeat.d.ts +4 -0
  145. package/dist/heartbeat.js +101 -0
  146. package/dist/index.d.ts +1 -0
  147. package/dist/index.js +75 -0
  148. package/dist/langfuse.d.ts +34 -0
  149. package/dist/langfuse.js +145 -0
  150. package/dist/mcp-context-a8c.d.ts +13 -0
  151. package/dist/mcp-context-a8c.js +34 -0
  152. package/dist/model-selection.d.ts +18 -0
  153. package/dist/model-selection.js +50 -0
  154. package/dist/orchestrator.d.ts +15 -0
  155. package/dist/orchestrator.js +676 -0
  156. package/dist/providers/anthropic.d.ts +7 -0
  157. package/dist/providers/anthropic.js +319 -0
  158. package/dist/providers/codex.d.ts +17 -0
  159. package/dist/providers/codex.js +508 -0
  160. package/dist/providers/content.d.ts +21 -0
  161. package/dist/providers/content.js +55 -0
  162. package/dist/providers/index.d.ts +13 -0
  163. package/dist/providers/index.js +138 -0
  164. package/dist/providers/observability.d.ts +19 -0
  165. package/dist/providers/observability.js +94 -0
  166. package/dist/providers/openai.d.ts +10 -0
  167. package/dist/providers/openai.js +310 -0
  168. package/dist/providers/tool-guard.d.ts +30 -0
  169. package/dist/providers/tool-guard.js +89 -0
  170. package/dist/providers/types.d.ts +34 -0
  171. package/dist/providers/types.js +2 -0
  172. package/dist/providers/utils.d.ts +65 -0
  173. package/dist/providers/utils.js +199 -0
  174. package/dist/security.d.ts +8 -0
  175. package/dist/security.js +113 -0
  176. package/dist/service.d.ts +8 -0
  177. package/dist/service.js +38 -0
  178. package/dist/sessions.d.ts +35 -0
  179. package/dist/sessions.js +142 -0
  180. package/dist/setup.d.ts +36 -0
  181. package/dist/setup.js +821 -0
  182. package/dist/skills-types.d.ts +65 -0
  183. package/dist/skills-types.js +2 -0
  184. package/dist/skills.d.ts +32 -0
  185. package/dist/skills.js +260 -0
  186. package/dist/subagent.d.ts +19 -0
  187. package/dist/subagent.js +376 -0
  188. package/dist/telegram.d.ts +2 -0
  189. package/dist/telegram.js +11 -0
  190. package/dist/tools/bash-tool.d.ts +3 -0
  191. package/dist/tools/bash-tool.js +59 -0
  192. package/dist/tools/browser-tool.d.ts +3 -0
  193. package/dist/tools/browser-tool.js +265 -0
  194. package/dist/tools/definitions.d.ts +432 -0
  195. package/dist/tools/definitions.js +181 -0
  196. package/dist/tools/execute-context.d.ts +26 -0
  197. package/dist/tools/execute-context.js +1 -0
  198. package/dist/tools/file-tools.d.ts +8 -0
  199. package/dist/tools/file-tools.js +67 -0
  200. package/dist/tools/path-utils.d.ts +1 -0
  201. package/dist/tools/path-utils.js +8 -0
  202. package/dist/tools.d.ts +24 -0
  203. package/dist/tools.js +281 -0
  204. package/dist/types.d.ts +259 -0
  205. package/dist/types.js +2 -0
  206. package/dist/usage.d.ts +76 -0
  207. package/dist/usage.js +150 -0
  208. package/dist/voice.d.ts +37 -0
  209. package/dist/voice.js +461 -0
  210. package/package.json +70 -0
  211. package/templates/AGENTS.md +38 -0
  212. package/templates/BOOT.md +23 -0
  213. package/templates/BOOTSTRAP.md +26 -0
  214. package/templates/HEARTBEAT.md +5 -0
  215. package/templates/IDENTITY.md +5 -0
  216. package/templates/MEMORY.md +24 -0
  217. package/templates/SOUL.md +92 -0
  218. package/templates/TOOLS.md +30 -0
  219. package/templates/USER.md +31 -0
@@ -0,0 +1,65 @@
1
+ /** YAML frontmatter fields in a SKILL.md file */
2
+ export interface SkillFrontmatter {
3
+ /** Unique skill name (defaults to directory name) */
4
+ name: string;
5
+ /** Short description shown in skill listing */
6
+ description: string;
7
+ /** Whether the skill is active (default: true) */
8
+ enabled?: boolean;
9
+ /** Emoji identifier for the skill */
10
+ emoji?: string;
11
+ /** Freeform tags for filtering */
12
+ tags?: string[];
13
+ /** External requirements that must be met for eligibility */
14
+ requires?: SkillRequirements;
15
+ /** Context filters — when this skill should activate */
16
+ contexts?: SkillContext;
17
+ /** Sort priority — lower numbers appear first (default: 100) */
18
+ priority?: number;
19
+ }
20
+ /** External requirements for a skill to be eligible */
21
+ export interface SkillRequirements {
22
+ /** Binary names that must be on PATH (checked via `which`) */
23
+ bins?: string[];
24
+ /** Environment variables that must be set */
25
+ env?: string[];
26
+ /** Tool names that must be available in the current ToolConfig */
27
+ tools?: string[];
28
+ /** File/directory paths that must exist */
29
+ paths?: string[];
30
+ }
31
+ /** Context filters for when a skill should be injected */
32
+ export interface SkillContext {
33
+ /** Channel names where this skill applies (e.g. "telegram", "discord") */
34
+ channels?: string[];
35
+ /** Cron job IDs where this skill applies */
36
+ cronJobs?: string[];
37
+ /** Tag-based filters — skill activates when any tag matches */
38
+ tags?: string[];
39
+ }
40
+ /** A fully loaded skill after parsing and eligibility check */
41
+ export interface LoadedSkill {
42
+ /** Skill name (from frontmatter or directory name) */
43
+ name: string;
44
+ /** Absolute path to the skill directory */
45
+ dirPath: string;
46
+ /** Parsed frontmatter */
47
+ frontmatter: SkillFrontmatter;
48
+ /** Markdown body (after frontmatter) */
49
+ body: string;
50
+ /** Whether the skill passed eligibility checks */
51
+ eligible: boolean;
52
+ /** Reason for ineligibility (undefined if eligible) */
53
+ reason?: string;
54
+ }
55
+ /** Config section for the skills system */
56
+ export interface SkillConfig {
57
+ /** Master switch for the skills system (default: true) */
58
+ enabled?: boolean;
59
+ /** Skills directory path (default: ~/.skimpyclaw/skills/) */
60
+ directory?: string;
61
+ /** Override per-skill enabled state: { skillName: boolean } */
62
+ entries?: Record<string, boolean>;
63
+ /** Max approximate tokens for injected skills prompt (default: 4000) */
64
+ maxPromptTokens?: number;
65
+ }
@@ -0,0 +1,2 @@
1
+ // Skills System Type Definitions
2
+ export {};
@@ -0,0 +1,32 @@
1
+ import type { SkillFrontmatter, LoadedSkill, SkillConfig } from './skills-types.js';
2
+ import type { ToolConfig } from './types.js';
3
+ /**
4
+ * Check whether a skill meets its external requirements.
5
+ * Returns { eligible: true } or { eligible: false, reason: string }.
6
+ */
7
+ export declare function checkEligibility(skill: SkillFrontmatter, toolConfig?: ToolConfig): {
8
+ eligible: boolean;
9
+ reason?: string;
10
+ };
11
+ /**
12
+ * Scan the skills directory and load all valid skills.
13
+ * Returns skills sorted by priority (lower first).
14
+ * Results are cached for 60s to avoid repeated disk I/O and `which` calls.
15
+ */
16
+ export declare function loadSkills(skillConfig?: SkillConfig, toolConfig?: ToolConfig): LoadedSkill[];
17
+ export declare function clearSkillsCache(): void;
18
+ /**
19
+ * Filter skills by context (channel, cron job ID, tags).
20
+ * Skills with no context filters pass through (always active).
21
+ * Skills with context filters must match at least one criterion.
22
+ */
23
+ export declare function getSkillsForContext(skills: LoadedSkill[], context?: {
24
+ channel?: string;
25
+ cronJobId?: string;
26
+ tags?: string[];
27
+ }): LoadedSkill[];
28
+ /**
29
+ * Format eligible, context-filtered skills into a markdown prompt section.
30
+ * Respects maxPromptTokens budget (approximate).
31
+ */
32
+ export declare function formatSkillsPrompt(skills: LoadedSkill[], maxTokens?: number): string;
package/dist/skills.js ADDED
@@ -0,0 +1,260 @@
1
+ // Skills System: Load, filter, and format skill prompts from ~/.skimpyclaw/skills/
2
+ import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { execSync } from 'child_process';
6
+ import matter from 'gray-matter';
7
+ import { TTLCache } from './cache.js';
8
+ const DEFAULT_SKILLS_DIR = join(homedir(), '.skimpyclaw', 'skills');
9
+ const DEFAULT_PRIORITY = 100;
10
+ const DEFAULT_MAX_PROMPT_TOKENS = 4000;
11
+ // Rough chars-per-token estimate for prompt budgeting
12
+ const CHARS_PER_TOKEN = 4;
13
+ /**
14
+ * Check if a binary exists on PATH.
15
+ * Returns true if found, false otherwise.
16
+ */
17
+ function binExists(name) {
18
+ try {
19
+ execSync(`which ${name}`, { stdio: 'ignore' });
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ /**
27
+ * Check whether a skill meets its external requirements.
28
+ * Returns { eligible: true } or { eligible: false, reason: string }.
29
+ */
30
+ export function checkEligibility(skill, toolConfig) {
31
+ const reqs = skill.requires;
32
+ if (!reqs)
33
+ return { eligible: true };
34
+ // Check binary dependencies
35
+ if (reqs.bins) {
36
+ for (const bin of reqs.bins) {
37
+ if (!binExists(bin)) {
38
+ return { eligible: false, reason: `Missing binary: ${bin}` };
39
+ }
40
+ }
41
+ }
42
+ // Check environment variables
43
+ if (reqs.env) {
44
+ for (const envVar of reqs.env) {
45
+ if (!process.env[envVar]) {
46
+ return { eligible: false, reason: `Missing env var: ${envVar}` };
47
+ }
48
+ }
49
+ }
50
+ // Check file/directory paths
51
+ if (reqs.paths) {
52
+ for (const p of reqs.paths) {
53
+ if (!existsSync(p)) {
54
+ return { eligible: false, reason: `Missing path: ${p}` };
55
+ }
56
+ }
57
+ }
58
+ // Check tool availability against the active ToolConfig
59
+ if (reqs.tools && reqs.tools.length > 0) {
60
+ if (!toolConfig?.enabled) {
61
+ return { eligible: false, reason: `Tools not enabled (needs: ${reqs.tools.join(', ')})` };
62
+ }
63
+ const missing = [];
64
+ for (const tool of reqs.tools) {
65
+ const t = tool.toLowerCase();
66
+ if (t === 'browser') {
67
+ if (!toolConfig.browser?.enabled)
68
+ missing.push(tool);
69
+ }
70
+ // spawn_subagent and other built-ins are available whenever tools.enabled is true
71
+ }
72
+ if (missing.length > 0) {
73
+ return { eligible: false, reason: `Tools not enabled (needs: ${missing.join(', ')})` };
74
+ }
75
+ }
76
+ return { eligible: true };
77
+ }
78
+ /**
79
+ * Parse a single SKILL.md file from a skill directory.
80
+ * Returns a LoadedSkill or null if the file doesn't exist or is malformed.
81
+ */
82
+ function parseSkillFile(dirPath, dirName, skillConfig, toolConfig) {
83
+ const skillPath = join(dirPath, 'SKILL.md');
84
+ if (!existsSync(skillPath)) {
85
+ return null;
86
+ }
87
+ try {
88
+ const raw = readFileSync(skillPath, 'utf-8');
89
+ const { data, content } = matter(raw);
90
+ const frontmatter = {
91
+ name: data.name || dirName,
92
+ description: data.description || '',
93
+ enabled: data.enabled !== false, // default true
94
+ emoji: data.emoji,
95
+ tags: data.tags,
96
+ requires: data.requires,
97
+ contexts: data.contexts,
98
+ priority: typeof data.priority === 'number' ? data.priority : DEFAULT_PRIORITY,
99
+ };
100
+ // Check config-level override for this skill
101
+ if (skillConfig?.entries?.[frontmatter.name] === false) {
102
+ return {
103
+ name: frontmatter.name,
104
+ dirPath,
105
+ frontmatter,
106
+ body: content.trim(),
107
+ eligible: false,
108
+ reason: 'Disabled in config',
109
+ };
110
+ }
111
+ // Check frontmatter-level enabled flag
112
+ if (frontmatter.enabled === false) {
113
+ return {
114
+ name: frontmatter.name,
115
+ dirPath,
116
+ frontmatter,
117
+ body: content.trim(),
118
+ eligible: false,
119
+ reason: 'Disabled in frontmatter',
120
+ };
121
+ }
122
+ // Check external requirements
123
+ const eligibility = checkEligibility(frontmatter, toolConfig);
124
+ return {
125
+ name: frontmatter.name,
126
+ dirPath,
127
+ frontmatter,
128
+ body: content.trim(),
129
+ eligible: eligibility.eligible,
130
+ reason: eligibility.reason,
131
+ };
132
+ }
133
+ catch (err) {
134
+ const msg = err instanceof Error ? err.message : String(err);
135
+ console.warn(`[skills] Failed to parse ${skillPath}: ${msg}`);
136
+ return null;
137
+ }
138
+ }
139
+ const skillsCache = new TTLCache(60_000);
140
+ /**
141
+ * Scan the skills directory and load all valid skills.
142
+ * Returns skills sorted by priority (lower first).
143
+ * Results are cached for 60s to avoid repeated disk I/O and `which` calls.
144
+ */
145
+ export function loadSkills(skillConfig, toolConfig) {
146
+ // Master switch
147
+ if (skillConfig?.enabled === false) {
148
+ return [];
149
+ }
150
+ const cacheKey = JSON.stringify({
151
+ dir: skillConfig?.directory,
152
+ entries: skillConfig?.entries,
153
+ enabled: skillConfig?.enabled,
154
+ toolEnabled: toolConfig?.enabled,
155
+ browserEnabled: toolConfig?.browser?.enabled,
156
+ });
157
+ const cached = skillsCache.get(cacheKey);
158
+ if (cached)
159
+ return cached;
160
+ const dir = skillConfig?.directory || DEFAULT_SKILLS_DIR;
161
+ if (!existsSync(dir)) {
162
+ return [];
163
+ }
164
+ const dirEntries = readdirSync(dir);
165
+ const skills = [];
166
+ for (const entry of dirEntries) {
167
+ const entryPath = join(dir, entry);
168
+ // Only process directories
169
+ try {
170
+ if (!statSync(entryPath).isDirectory())
171
+ continue;
172
+ }
173
+ catch {
174
+ continue;
175
+ }
176
+ const skill = parseSkillFile(entryPath, entry, skillConfig, toolConfig);
177
+ if (skill) {
178
+ skills.push(skill);
179
+ }
180
+ }
181
+ // Sort by priority (lower = first), then alphabetically by name
182
+ skills.sort((a, b) => {
183
+ const pa = a.frontmatter.priority ?? DEFAULT_PRIORITY;
184
+ const pb = b.frontmatter.priority ?? DEFAULT_PRIORITY;
185
+ if (pa !== pb)
186
+ return pa - pb;
187
+ return a.name.localeCompare(b.name);
188
+ });
189
+ skillsCache.set(cacheKey, skills);
190
+ return skills;
191
+ }
192
+ export function clearSkillsCache() {
193
+ skillsCache.clear();
194
+ }
195
+ /**
196
+ * Filter skills by context (channel, cron job ID, tags).
197
+ * Skills with no context filters pass through (always active).
198
+ * Skills with context filters must match at least one criterion.
199
+ */
200
+ export function getSkillsForContext(skills, context) {
201
+ return skills.filter(skill => {
202
+ // Only include eligible skills
203
+ if (!skill.eligible)
204
+ return false;
205
+ const ctx = skill.frontmatter.contexts;
206
+ // No context filters = always active
207
+ if (!ctx)
208
+ return true;
209
+ const hasFilters = (ctx.channels && ctx.channels.length > 0) ||
210
+ (ctx.cronJobs && ctx.cronJobs.length > 0) ||
211
+ (ctx.tags && ctx.tags.length > 0);
212
+ // No actual filter values = always active
213
+ if (!hasFilters)
214
+ return true;
215
+ // Check channel match
216
+ if (ctx.channels?.length && context?.channel) {
217
+ if (ctx.channels.includes(context.channel))
218
+ return true;
219
+ }
220
+ // Check cron job match
221
+ if (ctx.cronJobs?.length && context?.cronJobId) {
222
+ if (ctx.cronJobs.includes(context.cronJobId))
223
+ return true;
224
+ }
225
+ // Check tag match (any overlap)
226
+ if (ctx.tags?.length && context?.tags?.length) {
227
+ if (ctx.tags.some(t => context.tags.includes(t)))
228
+ return true;
229
+ }
230
+ // Has filters but none matched
231
+ return false;
232
+ });
233
+ }
234
+ /**
235
+ * Format eligible, context-filtered skills into a markdown prompt section.
236
+ * Respects maxPromptTokens budget (approximate).
237
+ */
238
+ export function formatSkillsPrompt(skills, maxTokens) {
239
+ if (skills.length === 0)
240
+ return '';
241
+ const budget = (maxTokens ?? DEFAULT_MAX_PROMPT_TOKENS) * CHARS_PER_TOKEN;
242
+ const sections = [];
243
+ let totalChars = 0;
244
+ // Header
245
+ const header = '## Active Skills\n';
246
+ totalChars += header.length;
247
+ for (const skill of skills) {
248
+ const emoji = skill.frontmatter.emoji ? `${skill.frontmatter.emoji} ` : '';
249
+ const section = `### ${emoji}${skill.name}\n\n${skill.body}`;
250
+ if (totalChars + section.length > budget) {
251
+ console.warn(`[skills] Token budget exceeded, skipping remaining skills (included ${sections.length}/${skills.length})`);
252
+ break;
253
+ }
254
+ sections.push(section);
255
+ totalChars += section.length;
256
+ }
257
+ if (sections.length === 0)
258
+ return '';
259
+ return header + sections.join('\n\n---\n\n');
260
+ }
@@ -0,0 +1,19 @@
1
+ import type { Config, SubagentType, SubagentTask, ChatMessage } from './types.js';
2
+ /**
3
+ * Ensure agent directory and templates exist for a subagent type.
4
+ * Creates the dir + starter IDENTITY.md + TOOLS.md if missing.
5
+ * Registers the agent in config.agents.list (in-memory only).
6
+ */
7
+ export declare function ensureAgentSetup(type: SubagentType, config: Config): void;
8
+ export declare function initSubagentSystem(deliverFn: (chatId: number, message: string) => Promise<void>): void;
9
+ export declare function getPresetDescriptions(): string;
10
+ export declare function dispatchSubagent(type: SubagentType, prompt: string, chatId: number, config: Config, modelOverride?: string, history?: ChatMessage[], options?: {
11
+ label?: string;
12
+ allowedPaths?: string[];
13
+ maxRetries?: number;
14
+ }): SubagentTask;
15
+ export declare function cancelTask(id: string): SubagentTask | null;
16
+ export declare function getActiveTasks(): SubagentTask[];
17
+ export declare function getRecentTasks(n?: number): SubagentTask[];
18
+ export declare function getTask(id: string): SubagentTask | null;
19
+ export declare function resetForTesting(): void;