skimpyclaw 0.3.14 → 0.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 (222) hide show
  1. package/README.md +47 -37
  2. package/dist/__tests__/adapter-types.test.d.ts +4 -0
  3. package/dist/__tests__/adapter-types.test.js +63 -0
  4. package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
  5. package/dist/__tests__/anthropic-adapter.test.js +264 -0
  6. package/dist/__tests__/api.test.js +0 -1
  7. package/dist/__tests__/cli.integration.test.js +2 -4
  8. package/dist/__tests__/cli.test.js +0 -1
  9. package/dist/__tests__/code-agents-notifications.test.js +137 -0
  10. package/dist/__tests__/code-agents-parser.test.js +19 -1
  11. package/dist/__tests__/code-agents-preflight.test.js +3 -28
  12. package/dist/__tests__/code-agents-utils.test.js +34 -9
  13. package/dist/__tests__/code-agents-worktrees.test.js +116 -0
  14. package/dist/__tests__/codex-adapter.test.js +184 -0
  15. package/dist/__tests__/codex-auth.test.js +66 -0
  16. package/dist/__tests__/codex-provider-gating.test.js +35 -0
  17. package/dist/__tests__/codex-unified-loop.test.js +111 -0
  18. package/dist/__tests__/config-security.test.js +127 -0
  19. package/dist/__tests__/config.test.js +23 -0
  20. package/dist/__tests__/context-manager.test.js +243 -164
  21. package/dist/__tests__/cron-run.test.js +250 -0
  22. package/dist/__tests__/cron.test.js +12 -38
  23. package/dist/__tests__/digests.test.js +67 -0
  24. package/dist/__tests__/discord-attachments.test.js +211 -0
  25. package/dist/__tests__/discord-docs.test.d.ts +1 -0
  26. package/dist/__tests__/discord-docs.test.js +27 -0
  27. package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
  28. package/dist/__tests__/discord-thread-agents.test.js +115 -0
  29. package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
  30. package/dist/__tests__/discord-thread-context.test.js +42 -0
  31. package/dist/__tests__/doctor.formatters.test.js +4 -4
  32. package/dist/__tests__/doctor.index.test.js +1 -1
  33. package/dist/__tests__/doctor.runner.test.js +3 -15
  34. package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
  35. package/dist/__tests__/env-sanitizer.test.js +45 -0
  36. package/dist/__tests__/exec-approval.test.js +61 -0
  37. package/dist/__tests__/fetch-tool.test.d.ts +1 -0
  38. package/dist/__tests__/fetch-tool.test.js +85 -0
  39. package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
  40. package/dist/__tests__/gateway-status-auth.test.js +72 -0
  41. package/dist/__tests__/heartbeat.test.js +3 -3
  42. package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
  43. package/dist/__tests__/interactive-sessions.test.js +96 -0
  44. package/dist/__tests__/langfuse.test.js +6 -18
  45. package/dist/__tests__/model-selection.test.js +3 -4
  46. package/dist/__tests__/providers-init.test.js +2 -8
  47. package/dist/__tests__/providers-routing.test.js +1 -1
  48. package/dist/__tests__/providers-utils.test.js +13 -3
  49. package/dist/__tests__/sessions.test.js +14 -10
  50. package/dist/__tests__/setup.test.js +12 -29
  51. package/dist/__tests__/skills.test.js +10 -7
  52. package/dist/__tests__/stream-formatter.test.d.ts +1 -0
  53. package/dist/__tests__/stream-formatter.test.js +114 -0
  54. package/dist/__tests__/token-efficiency.test.js +131 -15
  55. package/dist/__tests__/tool-loop.test.d.ts +4 -0
  56. package/dist/__tests__/tool-loop.test.js +505 -0
  57. package/dist/__tests__/tools.test.js +101 -276
  58. package/dist/__tests__/utils.test.d.ts +1 -0
  59. package/dist/__tests__/utils.test.js +14 -0
  60. package/dist/__tests__/voice.test.js +21 -0
  61. package/dist/agent.js +35 -4
  62. package/dist/api.js +113 -37
  63. package/dist/channels/discord/attachments.d.ts +50 -0
  64. package/dist/channels/discord/attachments.js +137 -0
  65. package/dist/channels/discord/delegation.d.ts +5 -0
  66. package/dist/channels/discord/delegation.js +136 -0
  67. package/dist/channels/discord/handlers.js +694 -7
  68. package/dist/channels/discord/index.d.ts +16 -1
  69. package/dist/channels/discord/index.js +64 -1
  70. package/dist/channels/discord/thread-agents.d.ts +54 -0
  71. package/dist/channels/discord/thread-agents.js +323 -0
  72. package/dist/channels/discord/threads.d.ts +58 -0
  73. package/dist/channels/discord/threads.js +192 -0
  74. package/dist/channels/discord/types.js +4 -2
  75. package/dist/channels/discord/utils.d.ts +16 -0
  76. package/dist/channels/discord/utils.js +86 -6
  77. package/dist/channels/telegram/index.d.ts +1 -1
  78. package/dist/channels/telegram/types.js +1 -1
  79. package/dist/channels/telegram/utils.js +9 -3
  80. package/dist/channels.d.ts +1 -1
  81. package/dist/cli.js +20 -400
  82. package/dist/code-agents/executor.d.ts +1 -1
  83. package/dist/code-agents/executor.js +101 -45
  84. package/dist/code-agents/index.d.ts +2 -7
  85. package/dist/code-agents/index.js +111 -80
  86. package/dist/code-agents/interactive-resume.d.ts +6 -0
  87. package/dist/code-agents/interactive-resume.js +98 -0
  88. package/dist/code-agents/interactive-sessions.d.ts +20 -0
  89. package/dist/code-agents/interactive-sessions.js +132 -0
  90. package/dist/code-agents/parser.js +5 -1
  91. package/dist/code-agents/registry.d.ts +7 -1
  92. package/dist/code-agents/registry.js +11 -23
  93. package/dist/code-agents/stream-formatter.d.ts +8 -0
  94. package/dist/code-agents/stream-formatter.js +92 -0
  95. package/dist/code-agents/types.d.ts +16 -24
  96. package/dist/code-agents/utils.d.ts +35 -11
  97. package/dist/code-agents/utils.js +349 -95
  98. package/dist/code-agents/worktrees.d.ts +37 -0
  99. package/dist/code-agents/worktrees.js +116 -0
  100. package/dist/config.d.ts +2 -4
  101. package/dist/config.js +123 -23
  102. package/dist/cron.d.ts +1 -6
  103. package/dist/cron.js +175 -82
  104. package/dist/dashboard/assets/index-B345aOO-.js +65 -0
  105. package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/digests.d.ts +1 -0
  108. package/dist/digests.js +132 -42
  109. package/dist/doctor/checks.d.ts +0 -3
  110. package/dist/doctor/checks.js +1 -108
  111. package/dist/doctor/runner.js +1 -4
  112. package/dist/env-sanitizer.d.ts +2 -0
  113. package/dist/env-sanitizer.js +61 -0
  114. package/dist/exec-approval.d.ts +11 -1
  115. package/dist/exec-approval.js +17 -4
  116. package/dist/gateway.d.ts +3 -1
  117. package/dist/gateway.js +17 -7
  118. package/dist/heartbeat.js +1 -6
  119. package/dist/langfuse.js +3 -29
  120. package/dist/model-selection.js +3 -1
  121. package/dist/providers/adapter.d.ts +118 -0
  122. package/dist/providers/adapter.js +6 -0
  123. package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
  124. package/dist/providers/adapters/anthropic-adapter.js +204 -0
  125. package/dist/providers/adapters/codex-adapter.d.ts +26 -0
  126. package/dist/providers/adapters/codex-adapter.js +203 -0
  127. package/dist/providers/anthropic.d.ts +1 -0
  128. package/dist/providers/anthropic.js +10 -272
  129. package/dist/providers/codex.d.ts +21 -0
  130. package/dist/providers/codex.js +149 -330
  131. package/dist/providers/content.d.ts +1 -1
  132. package/dist/providers/content.js +2 -2
  133. package/dist/providers/context-manager.d.ts +18 -6
  134. package/dist/providers/context-manager.js +199 -223
  135. package/dist/providers/index.d.ts +9 -1
  136. package/dist/providers/index.js +73 -64
  137. package/dist/providers/loop-utils.d.ts +20 -0
  138. package/dist/providers/loop-utils.js +30 -0
  139. package/dist/providers/tool-loop.d.ts +12 -0
  140. package/dist/providers/tool-loop.js +251 -0
  141. package/dist/providers/utils.d.ts +19 -3
  142. package/dist/providers/utils.js +100 -29
  143. package/dist/secure-store.d.ts +8 -0
  144. package/dist/secure-store.js +80 -0
  145. package/dist/service.js +3 -28
  146. package/dist/sessions.d.ts +3 -0
  147. package/dist/sessions.js +147 -18
  148. package/dist/setup-templates.js +13 -25
  149. package/dist/setup.d.ts +10 -6
  150. package/dist/setup.js +84 -292
  151. package/dist/skills.js +3 -11
  152. package/dist/tools/agent-delegation.d.ts +19 -0
  153. package/dist/tools/agent-delegation.js +49 -0
  154. package/dist/tools/bash-tool.js +89 -34
  155. package/dist/tools/definitions.d.ts +199 -302
  156. package/dist/tools/definitions.js +70 -123
  157. package/dist/tools/execute-context.d.ts +13 -4
  158. package/dist/tools/fetch-tool.js +109 -13
  159. package/dist/tools/file-tools.js +7 -1
  160. package/dist/tools.d.ts +7 -7
  161. package/dist/tools.js +133 -151
  162. package/dist/types.d.ts +37 -30
  163. package/dist/utils.js +4 -6
  164. package/dist/voice.d.ts +1 -1
  165. package/dist/voice.js +17 -4
  166. package/package.json +33 -23
  167. package/templates/TOOLS.md +0 -27
  168. package/dist/__tests__/audit.test.js +0 -122
  169. package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
  170. package/dist/__tests__/code-agents-sandbox.test.js +0 -163
  171. package/dist/__tests__/orchestrator.test.js +0 -425
  172. package/dist/__tests__/sandbox-bridge.test.js +0 -116
  173. package/dist/__tests__/sandbox-manager.test.js +0 -144
  174. package/dist/__tests__/sandbox-mount-security.test.js +0 -139
  175. package/dist/__tests__/sandbox-runtime.test.js +0 -176
  176. package/dist/__tests__/subagent.test.js +0 -240
  177. package/dist/__tests__/telegram.test.js +0 -42
  178. package/dist/code-agents/orchestrator.d.ts +0 -29
  179. package/dist/code-agents/orchestrator.js +0 -694
  180. package/dist/code-agents/worktree.d.ts +0 -40
  181. package/dist/code-agents/worktree.js +0 -215
  182. package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
  183. package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
  184. package/dist/dashboard.d.ts +0 -8
  185. package/dist/dashboard.js +0 -4071
  186. package/dist/discord.d.ts +0 -8
  187. package/dist/discord.js +0 -792
  188. package/dist/mcp-context-a8c.d.ts +0 -13
  189. package/dist/mcp-context-a8c.js +0 -34
  190. package/dist/orchestrator.d.ts +0 -15
  191. package/dist/orchestrator.js +0 -676
  192. package/dist/providers/openai.d.ts +0 -10
  193. package/dist/providers/openai.js +0 -355
  194. package/dist/sandbox/bridge.d.ts +0 -5
  195. package/dist/sandbox/bridge.js +0 -63
  196. package/dist/sandbox/index.d.ts +0 -5
  197. package/dist/sandbox/index.js +0 -4
  198. package/dist/sandbox/manager.d.ts +0 -7
  199. package/dist/sandbox/manager.js +0 -100
  200. package/dist/sandbox/mount-security.d.ts +0 -12
  201. package/dist/sandbox/mount-security.js +0 -122
  202. package/dist/sandbox/runtime.d.ts +0 -39
  203. package/dist/sandbox/runtime.js +0 -192
  204. package/dist/sandbox-utils.d.ts +0 -6
  205. package/dist/sandbox-utils.js +0 -36
  206. package/dist/subagent.d.ts +0 -19
  207. package/dist/subagent.js +0 -407
  208. package/dist/telegram.d.ts +0 -2
  209. package/dist/telegram.js +0 -11
  210. package/dist/tools/browser-tool.d.ts +0 -3
  211. package/dist/tools/browser-tool.js +0 -266
  212. package/sandbox/Dockerfile +0 -40
  213. /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
  214. /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
  215. /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
  216. /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
  217. /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
  218. /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
  219. /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
  220. /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
  221. /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
  222. /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
package/dist/subagent.js DELETED
@@ -1,407 +0,0 @@
1
- // Subagent system: background task dispatch with concurrency control
2
- import { homedir } from 'os';
3
- import { join } from 'path';
4
- import { existsSync, mkdirSync, writeFileSync, appendFileSync } from 'fs';
5
- import { runAgentTurn } from './agent.js';
6
- import { getCurrentModel } from './gateway.js';
7
- import { getAgentDir } from './config.js';
8
- import { releaseAllLocks } from './file-lock.js';
9
- import { resolveModel } from './providers/utils.js';
10
- const DEFAULT_MAX_CONCURRENT = 5;
11
- const DEFAULT_MAX_RETRIES = 2;
12
- const REGISTRY_PATH = join(homedir(), '.skimpyclaw', 'logs', 'subagent-runs.jsonl');
13
- const PRESETS = {
14
- coding: {
15
- agentId: 'coding',
16
- defaultModel: 'claude-opus',
17
- toolConfig: {
18
- enabled: true,
19
- allowedPaths: [join(homedir(), '.skimpyclaw')],
20
- maxIterations: 50,
21
- bashTimeout: 30000,
22
- toolProfile: 'minimal',
23
- },
24
- description: 'Code tasks with broad file + bash access'
25
- },
26
- research: {
27
- agentId: 'research',
28
- defaultModel: 'claude-think',
29
- toolConfig: {
30
- enabled: true,
31
- allowedPaths: [join(homedir(), '.skimpyclaw')],
32
- maxIterations: 30,
33
- bashTimeout: 15000,
34
- toolProfile: 'minimal',
35
- },
36
- description: 'Research tasks with configurable file access'
37
- },
38
- };
39
- // --- Starter Templates ---
40
- const STARTER_TEMPLATES = {
41
- coding: {
42
- identity: `# IDENTITY.md - Coding Subagent
43
-
44
- Name: Coding Agent
45
- Emoji: 🔧
46
-
47
- You are a coding subagent dispatched for a specific task. You have file and bash access
48
- within your allowed paths (provided at runtime).
49
-
50
- ## Your Role
51
- - Execute coding tasks: write code, fix bugs, refactor, run commands
52
- - Act on instructions directly — don't narrate what you're going to do
53
- - Return concise results when done
54
-
55
- ## Your Limitations
56
- - You are short-lived: complete the task and return the result
57
- - You have ONLY 4 tools: Read, Write, Glob, Bash
58
- - Do NOT invent or hallucinate tools that don't exist
59
- `,
60
- tools: `# TOOLS.md - Coding Subagent Tools
61
-
62
- ## CRITICAL: Act, Don't Narrate
63
-
64
- **NEVER say "let me write the code" or "now I'll update the file" — just call the tool.**
65
- You have a LIMITED number of tool iterations. Every message you spend talking about what
66
- you're going to do is one less chance to actually do it.
67
-
68
- ## Tool Priority — Use Native Tools First
69
-
70
- **For file operations, always prefer native tools over bash scripts. They are faster, safer, and use fewer iterations.**
71
-
72
- | Task | Use this | NOT this |
73
- |------|----------|----------|
74
- | List files/dirs | \`Glob\` | \`ls\`, \`find\`, \`python3\` scripts |
75
- | Read a file | \`Read\` | \`cat\`, \`python3\` scripts |
76
- | Write a file | \`Write\` | \`echo >\`, \`tee\`, \`python3\` scripts |
77
- | Run builds/tests | \`Bash\` | — |
78
- | Git operations | \`Bash\` | — |
79
- | Install packages | \`Bash\` | — |
80
-
81
- **NEVER use \`python3 -\`, \`perl -e\`, \`ruby -e\`, or other inline interpreter scripts to explore the filesystem. Use Glob and Read instead.**
82
-
83
- ## Your 4 Tools
84
-
85
- ### Read
86
- Read the contents of a file. Parameter: \`file_path\` (string, required)
87
-
88
- ### Write
89
- Write content to a file. Parameters: \`file_path\` (string, required), \`content\` (string, required)
90
-
91
- ### Glob
92
- List files and directories at a path. Parameter: \`path\` (string, required)
93
-
94
- ### Bash
95
- Execute a shell command. Parameters: \`command\` (string, required), \`cwd\` (string, optional)
96
- Reserved for: builds, tests, git, package managers, and commands that have no native tool equivalent.
97
-
98
- ## Key Paths
99
- - Config: ~/.skimpyclaw/config.json
100
- - Agent templates: ~/.skimpyclaw/agents/
101
- `
102
- },
103
- research: {
104
- identity: `# IDENTITY.md - Research Subagent
105
-
106
- Name: Research Agent
107
- Emoji: 🔍
108
-
109
- You are a research subagent dispatched for a specific task.
110
-
111
- ## Your Role
112
- - Research questions using vault notes and files
113
- - Summarize findings concisely
114
- - Act on instructions directly — don't narrate what you're going to do
115
-
116
- ## Your Limitations
117
- - You are short-lived: complete the task and return the result
118
- - You have ONLY 4 tools: Read, Write, Glob, Bash
119
- - Do NOT invent or hallucinate tools that don't exist
120
- `,
121
- tools: `# TOOLS.md - Research Subagent Tools
122
-
123
- ## CRITICAL: Act, Don't Narrate
124
-
125
- **NEVER say "let me check" or "I'll look into that" — just call the tool.**
126
-
127
- ## Tool Priority — Use Native Tools First
128
-
129
- **For file operations, always prefer native tools over bash scripts.**
130
-
131
- | Task | Use this | NOT this |
132
- |------|----------|----------|
133
- | List files/dirs | \`Glob\` | \`ls\`, \`find\`, \`python3\` scripts |
134
- | Read a file | \`Read\` | \`cat\`, \`python3\` scripts |
135
- | Write a file | \`Write\` | \`echo >\`, \`tee\` |
136
-
137
- **NEVER use \`python3 -\`, \`perl -e\`, or other inline interpreter scripts to explore the filesystem.**
138
-
139
- ## Your 4 Tools
140
-
141
- ### Read
142
- Read the contents of a file. Parameter: \`file_path\` (string, required)
143
-
144
- ### Write
145
- Write content to a file. Parameters: \`file_path\` (string, required), \`content\` (string, required)
146
-
147
- ### Glob
148
- List files and directories at a path. Parameter: \`path\` (string, required)
149
-
150
- ### Bash
151
- Execute a shell command. Parameters: \`command\` (string, required), \`cwd\` (string, optional)
152
- Reserved for: git, package managers, and commands with no native tool equivalent.
153
-
154
- ## Key Paths
155
- - Config: ~/.skimpyclaw/config.json
156
- - Agent templates: ~/.skimpyclaw/agents/
157
- - Add your notes/vault paths to allowedPaths in config before using them
158
- `
159
- },
160
- };
161
- // Agent identity metadata for in-memory registration
162
- const AGENT_IDENTITIES = {
163
- coding: { name: 'Coding Agent', emoji: '🔧' },
164
- research: { name: 'Research Agent', emoji: '🔍' }
165
- };
166
- /**
167
- * Ensure agent directory and templates exist for a subagent type.
168
- * Creates the dir + starter IDENTITY.md + TOOLS.md if missing.
169
- * Registers the agent in config.agents.list (in-memory only).
170
- */
171
- export function ensureAgentSetup(type, config) {
172
- const preset = PRESETS[type];
173
- const agentDir = getAgentDir(preset.agentId);
174
- // Create dir + starter templates if missing
175
- if (!existsSync(agentDir)) {
176
- mkdirSync(agentDir, { recursive: true });
177
- const templates = STARTER_TEMPLATES[type];
178
- writeFileSync(join(agentDir, 'IDENTITY.md'), templates.identity, 'utf-8');
179
- writeFileSync(join(agentDir, 'TOOLS.md'), templates.tools, 'utf-8');
180
- console.log(`[subagent] Created agent dir with templates: ${agentDir}`);
181
- }
182
- // Register in config.agents.list if not already there (in-memory only)
183
- if (!config.agents.list[preset.agentId]) {
184
- const identity = AGENT_IDENTITIES[type];
185
- config.agents.list[preset.agentId] = {
186
- identity,
187
- model: resolveModel(preset.defaultModel || 'anthropic/claude-sonnet-4-5', config),
188
- thinking: 'medium'
189
- };
190
- console.log(`[subagent] Registered agent in config: ${preset.agentId}`);
191
- }
192
- }
193
- // Task tracking
194
- let taskCounter = 0;
195
- const tasks = new Map();
196
- let deliverMessage = null;
197
- export function initSubagentSystem(deliverFn) {
198
- deliverMessage = deliverFn;
199
- console.log('[subagent] System initialized');
200
- }
201
- export function getPresetDescriptions() {
202
- return Object.entries(PRESETS)
203
- .map(([type, preset]) => ` ${type} — ${preset.description} (default: ${preset.defaultModel || 'current model'})`)
204
- .join('\n');
205
- }
206
- // --- Disk Registry ---
207
- function ensureRegistryDir() {
208
- const dir = join(homedir(), '.skimpyclaw', 'logs');
209
- if (!existsSync(dir)) {
210
- mkdirSync(dir, { recursive: true });
211
- }
212
- }
213
- function appendRegistryEvent(event) {
214
- try {
215
- ensureRegistryDir();
216
- const line = JSON.stringify({ ...event, timestamp: new Date().toISOString() }) + '\n';
217
- appendFileSync(REGISTRY_PATH, line, 'utf-8');
218
- }
219
- catch (err) {
220
- console.warn('[subagent] Failed to write registry event:', err instanceof Error ? err.message : err);
221
- }
222
- }
223
- // --- Dispatch ---
224
- export function dispatchSubagent(type, prompt, chatId, config, modelOverride, history, options) {
225
- const maxConcurrent = config.subagents?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
226
- const active = [...tasks.values()].filter((t) => t.status === 'running' || t.status === 'pending');
227
- if (active.length >= maxConcurrent) {
228
- throw new Error(`Max concurrent agents reached (${maxConcurrent}). Use /tasks to see running agents or /cancel to stop one.`);
229
- }
230
- const preset = PRESETS[type];
231
- if (!preset) {
232
- throw new Error(`Unknown agent type: ${type}. Use: ${Object.keys(PRESETS).join(', ')}`);
233
- }
234
- taskCounter++;
235
- const id = `t${taskCounter}`;
236
- const model = resolveModel(modelOverride || preset.defaultModel || getCurrentModel(), config);
237
- const maxRetries = options?.maxRetries ?? config.subagents?.maxRetries ?? DEFAULT_MAX_RETRIES;
238
- const task = {
239
- id,
240
- type,
241
- prompt,
242
- status: 'pending',
243
- chatId,
244
- model,
245
- label: options?.label,
246
- createdAt: new Date(),
247
- retryCount: 0,
248
- maxRetries,
249
- abortController: new AbortController()
250
- };
251
- tasks.set(id, task);
252
- // Log to disk registry
253
- appendRegistryEvent({
254
- type: 'task_created',
255
- taskId: id,
256
- agentType: type,
257
- model,
258
- label: options?.label,
259
- prompt: prompt.slice(0, 500),
260
- });
261
- // Build tool config with merged paths
262
- const toolConfig = {
263
- ...preset.toolConfig,
264
- allowedPaths: [
265
- ...preset.toolConfig.allowedPaths,
266
- ...(options?.allowedPaths || []),
267
- ],
268
- };
269
- // Fire and forget — don't await
270
- executeTask(task, config, toolConfig, history).catch((err) => {
271
- console.error(`[subagent] Unhandled error in task ${id}:`, err);
272
- });
273
- return task;
274
- }
275
- async function executeTask(task, config, toolConfig, history) {
276
- task.status = 'running';
277
- task.startedAt = new Date();
278
- const label = task.label ? ` "${task.label}"` : '';
279
- console.log(`[subagent] Starting ${task.id}${label} (${task.type}, model: ${task.model})`);
280
- try {
281
- // Check cancellation before starting
282
- if (task.abortController.signal.aborted) {
283
- task.status = 'cancelled';
284
- task.completedAt = new Date();
285
- appendRegistryEvent({ type: 'task_cancelled', taskId: task.id });
286
- return;
287
- }
288
- // Ensure agent dir + templates exist, register in config
289
- ensureAgentSetup(task.type, config);
290
- const response = await runAgentTurn(task.type, // agentId = preset.agentId = type name
291
- task.prompt, config, task.model, toolConfig, history, {
292
- channel: 'subagent',
293
- sessionId: task.id,
294
- metadata: {
295
- type: task.type,
296
- chatId: task.chatId,
297
- label: task.label,
298
- },
299
- abortSignal: task.abortController.signal,
300
- });
301
- // Check cancellation after completion
302
- if (task.abortController.signal.aborted) {
303
- task.status = 'cancelled';
304
- task.completedAt = new Date();
305
- releaseAllLocks(task.id);
306
- appendRegistryEvent({ type: 'task_cancelled', taskId: task.id });
307
- return;
308
- }
309
- task.status = 'completed';
310
- task.result = response;
311
- task.completedAt = new Date();
312
- // Release any file locks held by this task
313
- releaseAllLocks(task.id);
314
- const elapsed = Math.round((task.completedAt.getTime() - task.startedAt.getTime()) / 1000);
315
- console.log(`[subagent] Completed ${task.id}${label} in ${elapsed}s`);
316
- appendRegistryEvent({
317
- type: 'task_completed',
318
- taskId: task.id,
319
- elapsed,
320
- resultLength: response.length,
321
- });
322
- // Deliver result
323
- if (deliverMessage) {
324
- const labelStr = task.label ? ` (${task.label})` : '';
325
- const header = `✅ Agent ${task.id}${labelStr} completed in ${elapsed}s:`;
326
- await deliverMessage(task.chatId, `${header}\n\n${response}`);
327
- }
328
- }
329
- catch (error) {
330
- // Release any file locks on error
331
- releaseAllLocks(task.id);
332
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
333
- const retryCount = task.retryCount ?? 0;
334
- const maxRetries = task.maxRetries ?? DEFAULT_MAX_RETRIES;
335
- // Retry if under limit and not cancelled
336
- if (retryCount < maxRetries && !task.abortController.signal.aborted) {
337
- task.retryCount = retryCount + 1;
338
- const elapsed = Math.round((Date.now() - (task.startedAt?.getTime() || task.createdAt.getTime())) / 1000);
339
- console.log(`[subagent] Task ${task.id} failed after ${elapsed}s (attempt ${retryCount + 1}/${maxRetries + 1}), retrying: ${errorMsg}`);
340
- appendRegistryEvent({
341
- type: 'task_retry',
342
- taskId: task.id,
343
- attempt: task.retryCount,
344
- error: errorMsg,
345
- });
346
- if (deliverMessage) {
347
- await deliverMessage(task.chatId, `⚠️ Agent ${task.id} failed (attempt ${retryCount + 1}/${maxRetries + 1}), retrying...\n\nError: ${errorMsg}`);
348
- }
349
- // Modify prompt to include error context for retry (compressed to save tokens)
350
- const taskSummary = task.prompt.length > 500 ? task.prompt.slice(0, 500) + '...' : task.prompt;
351
- const retryPrompt = `Retry task (attempt ${(task.retryCount ?? 0) + 1}).\n\nTask: ${taskSummary}\n\nPrevious error: ${errorMsg}\n\nTry a different approach.`;
352
- task.prompt = retryPrompt;
353
- task.startedAt = new Date();
354
- // Retry
355
- return executeTask(task, config, {
356
- ...PRESETS[task.type].toolConfig,
357
- }, history);
358
- }
359
- task.status = 'failed';
360
- task.error = errorMsg;
361
- task.completedAt = new Date();
362
- const elapsed = Math.round((task.completedAt.getTime() -
363
- (task.startedAt?.getTime() || task.createdAt.getTime())) /
364
- 1000);
365
- console.error(`[subagent] Failed ${task.id} after ${elapsed}s (all retries exhausted):`, task.error);
366
- appendRegistryEvent({
367
- type: 'task_failed',
368
- taskId: task.id,
369
- elapsed,
370
- error: task.error,
371
- });
372
- if (deliverMessage) {
373
- await deliverMessage(task.chatId, `❌ Agent ${task.id} (${task.type}) failed after ${elapsed}s:\n\n${task.error}`);
374
- }
375
- }
376
- }
377
- export function cancelTask(id) {
378
- const task = tasks.get(id);
379
- if (!task)
380
- return null;
381
- if (task.status !== 'pending' && task.status !== 'running')
382
- return task;
383
- task.abortController.abort();
384
- task.status = 'cancelled';
385
- task.completedAt = new Date();
386
- releaseAllLocks(task.id);
387
- appendRegistryEvent({ type: 'task_cancelled', taskId: task.id });
388
- console.log(`[subagent] Cancelled ${task.id}`);
389
- return task;
390
- }
391
- export function getActiveTasks() {
392
- return [...tasks.values()].filter((t) => t.status === 'pending' || t.status === 'running');
393
- }
394
- export function getRecentTasks(n = 10) {
395
- return [...tasks.values()]
396
- .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
397
- .slice(0, n);
398
- }
399
- export function getTask(id) {
400
- return tasks.get(id) || null;
401
- }
402
- // For testing
403
- export function resetForTesting() {
404
- taskCounter = 0;
405
- tasks.clear();
406
- deliverMessage = null;
407
- }
@@ -1,2 +0,0 @@
1
- export { initTelegram, startTelegram, stopTelegram, getBot, setSilenceUntil, getSilenceUntil, isSilenced, sendProactiveMessage, sendProactiveVoice, getHistory, addToHistory, clearHistory, getRunContext, getDefaultTelegramToolConfig, buildHelpText, getTelegramDefaultChatId, startTypingIndicator, sendLongMessage, sendLongMessageHtml, escapeHtml, markdownToTelegramHtml, state, BOT_COMMANDS, LAUNCHD_LABEL, commandHandlers, subscribeToApprovalEvents, } from './channels/telegram/index.js';
2
- export type { MemoryFileInfo, TelegramContext } from './channels/telegram/types.js';
package/dist/telegram.js DELETED
@@ -1,11 +0,0 @@
1
- // Telegram bot - Re-export from new location for backward compatibility
2
- // @deprecated Import from './channels/telegram/index.js' instead
3
- export {
4
- // Core functions
5
- initTelegram, startTelegram, stopTelegram, getBot, setSilenceUntil, getSilenceUntil, isSilenced, sendProactiveMessage, sendProactiveVoice,
6
- // Utilities
7
- getHistory, addToHistory, clearHistory, getRunContext, getDefaultTelegramToolConfig, buildHelpText, getTelegramDefaultChatId, startTypingIndicator, sendLongMessage, sendLongMessageHtml, escapeHtml, markdownToTelegramHtml,
8
- // Constants
9
- state, BOT_COMMANDS, LAUNCHD_LABEL,
10
- // Handlers
11
- commandHandlers, subscribeToApprovalEvents, } from './channels/telegram/index.js';
@@ -1,3 +0,0 @@
1
- import type { ToolConfig } from '../types.js';
2
- export declare function executeBrowser(input: Record<string, any>, config: ToolConfig): Promise<string>;
3
- export declare function cleanupBrowser(): Promise<void>;
@@ -1,266 +0,0 @@
1
- import { existsSync, mkdirSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { homedir } from 'os';
4
- import { isPathAllowed } from './path-utils.js';
5
- import { toErrorMessage } from '../utils.js';
6
- let playwrightModule = null;
7
- let browserContext = null;
8
- let browserPage = null;
9
- let browserOptionsKey = null;
10
- async function getPlaywright() {
11
- if (!playwrightModule) {
12
- try {
13
- playwrightModule = await import('playwright');
14
- }
15
- catch {
16
- throw new Error('Playwright not installed. Run: npx playwright install');
17
- }
18
- }
19
- return playwrightModule;
20
- }
21
- function resolveScreenshotPath(filePath, config) {
22
- if (filePath) {
23
- if (!isPathAllowed(filePath, config.allowedPaths)) {
24
- throw new Error(`Path not allowed. Permitted: ${config.allowedPaths.join(', ')}`);
25
- }
26
- const dir = dirname(filePath);
27
- if (!existsSync(dir)) {
28
- mkdirSync(dir, { recursive: true });
29
- }
30
- return filePath;
31
- }
32
- const dir = join(homedir(), '.skimpyclaw', 'screenshots');
33
- if (!existsSync(dir)) {
34
- mkdirSync(dir, { recursive: true });
35
- }
36
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
37
- return join(dir, `shot-${stamp}.png`);
38
- }
39
- function isFileUrlAllowed(url, config) {
40
- if (!url.startsWith('file://'))
41
- return true;
42
- if (!config.browser?.allowFile)
43
- return false;
44
- const filePath = new URL(url).pathname;
45
- return isPathAllowed(filePath, config.allowedPaths);
46
- }
47
- /** Pick override only if it's a meaningful value (not empty string, not 0 for non-numeric fields). */
48
- function pick(override, configVal, fallback) {
49
- if (override !== undefined && override !== null && override !== '')
50
- return override;
51
- if (configVal !== undefined && configVal !== null && configVal !== '')
52
- return configVal;
53
- return fallback;
54
- }
55
- function buildBrowserOptions(config, overrides) {
56
- // Config values take priority for security/environment settings.
57
- // Overrides (from model tool calls) only apply when config doesn't specify a value.
58
- const type = pick(overrides?.type, config.browser?.type, 'chromium');
59
- const headless = config.browser?.headless ?? overrides?.headless ?? true;
60
- const slowMo = config.browser?.slowMoMs ?? ((typeof overrides?.slowMoMs === 'number' && overrides.slowMoMs > 0) ? overrides.slowMoMs : undefined);
61
- const userAgent = pick(config.browser?.userAgent, overrides?.userAgent);
62
- const viewport = config.browser?.viewport ?? (overrides?.viewport?.width ? overrides.viewport : undefined);
63
- // Security: profileDir and executablePath are config-only — never allow model overrides
64
- const profileDir = config.browser?.profileDir || join(homedir(), '.skimpyclaw', 'browser-profile');
65
- const executablePath = config.browser?.executablePath;
66
- return { type, headless, slowMo, userAgent, viewport, profileDir, executablePath };
67
- }
68
- async function ensureBrowser(config, overrides) {
69
- const options = buildBrowserOptions(config, overrides);
70
- const optionsKey = JSON.stringify(options);
71
- if (browserContext && browserPage && browserOptionsKey === optionsKey)
72
- return;
73
- if (browserPage) {
74
- await browserPage.close().catch(() => { });
75
- browserPage = null;
76
- }
77
- if (browserContext) {
78
- await browserContext.close().catch(() => { });
79
- browserContext = null;
80
- }
81
- if (!isPathAllowed(options.profileDir, config.allowedPaths)) {
82
- throw new Error(`Profile dir not allowed. Add to allowedPaths: ${options.profileDir}`);
83
- }
84
- if (!existsSync(options.profileDir)) {
85
- mkdirSync(options.profileDir, { recursive: true });
86
- }
87
- const pw = await getPlaywright();
88
- const browserLauncher = pw[options.type] || pw.chromium;
89
- try {
90
- browserContext = await browserLauncher.launchPersistentContext(options.profileDir, {
91
- headless: options.headless,
92
- slowMo: options.slowMo,
93
- userAgent: options.userAgent,
94
- viewport: options.viewport,
95
- executablePath: options.executablePath,
96
- args: [
97
- '--disable-blink-features=AutomationControlled',
98
- '--no-first-run',
99
- '--no-default-browser-check',
100
- ],
101
- ignoreDefaultArgs: ['--enable-automation', '--no-sandbox'],
102
- });
103
- }
104
- catch (err) {
105
- const msg = toErrorMessage(err);
106
- throw new Error(`Failed to launch browser (${options.type}): ${msg}. Ensure the browser is installed: npx playwright install ${options.type}`);
107
- }
108
- const pages = browserContext.pages();
109
- browserPage = pages.length > 0 ? pages[0] : await browserContext.newPage();
110
- // Remove navigator.webdriver flag that sites use to detect automation
111
- await browserPage.addInitScript(`Object.defineProperty(navigator, 'webdriver', { get: () => false })`);
112
- browserOptionsKey = optionsKey;
113
- }
114
- export async function executeBrowser(input, config) {
115
- if (!config.browser?.enabled) {
116
- return 'Error: Browser tool is disabled. Enable it in config (tools.browser.enabled).';
117
- }
118
- const action = String(input.action || '').toLowerCase();
119
- const timeoutMs = typeof input.timeoutMs === 'number' ? input.timeoutMs : 30_000;
120
- switch (action) {
121
- case 'open': {
122
- const url = input.url;
123
- if (!url)
124
- return 'Error: url is required for open.';
125
- if (!isFileUrlAllowed(url, config)) {
126
- return 'Error: file:// URLs are blocked. Enable tools.browser.allowFile to allow.';
127
- }
128
- await ensureBrowser(config, input);
129
- await browserPage.goto(url, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
130
- return `Opened: ${url}`;
131
- }
132
- case 'click': {
133
- if (!browserPage)
134
- return 'Error: Browser not open. Call open(url) first.';
135
- const selector = input.selector;
136
- if (!selector)
137
- return 'Error: selector is required for click.';
138
- await browserPage.click(selector, { timeout: timeoutMs });
139
- return `Clicked: ${selector}`;
140
- }
141
- case 'type': {
142
- if (!browserPage)
143
- return 'Error: Browser not open. Call open(url) first.';
144
- const selector = input.selector;
145
- const text = input.text;
146
- if (!selector || text === undefined)
147
- return 'Error: selector and text are required for type.';
148
- await browserPage.fill(selector, text, { timeout: timeoutMs });
149
- return `Typed into: ${selector}`;
150
- }
151
- case 'waitfor': {
152
- if (!browserPage)
153
- return 'Error: Browser not open. Call open(url) first.';
154
- const selector = input.selector;
155
- const text = input.text;
156
- if (!selector && !text)
157
- return 'Error: selector or text is required for waitFor.';
158
- if (selector) {
159
- await browserPage.waitForSelector(selector, { timeout: timeoutMs });
160
- return `Waited for selector: ${selector}`;
161
- }
162
- await browserPage.waitForSelector(`text=${text}`, { timeout: timeoutMs });
163
- return `Waited for text: ${text}`;
164
- }
165
- case 'select': {
166
- if (!browserPage)
167
- return 'Error: Browser not open. Call open(url) first.';
168
- const selector = input.selector;
169
- const value = input.text;
170
- if (!selector || value === undefined)
171
- return 'Error: selector and text (value) are required for select.';
172
- await browserPage.selectOption(selector, value, { timeout: timeoutMs });
173
- return `Selected "${value}" in: ${selector}`;
174
- }
175
- case 'hover': {
176
- if (!browserPage)
177
- return 'Error: Browser not open. Call open(url) first.';
178
- const selector = input.selector;
179
- if (!selector)
180
- return 'Error: selector is required for hover.';
181
- await browserPage.hover(selector, { timeout: timeoutMs });
182
- return `Hovered: ${selector}`;
183
- }
184
- case 'scroll': {
185
- if (!browserPage)
186
- return 'Error: Browser not open. Call open(url) first.';
187
- const selector = input.selector;
188
- if (selector) {
189
- await browserPage.evaluate(`{
190
- const el = document.querySelector(${JSON.stringify(selector)});
191
- if (el) el.scrollIntoView({ behavior: 'smooth' });
192
- }`);
193
- return `Scrolled into view: ${selector}`;
194
- }
195
- const direction = (input.direction || 'down').toLowerCase();
196
- const amount = typeof input.amount === 'number' ? input.amount : undefined;
197
- const dir = direction === 'up' ? -1 : 1;
198
- const scrollExpr = amount != null
199
- ? `window.scrollBy(0, ${dir * amount})`
200
- : `window.scrollBy(0, ${dir} * window.innerHeight)`;
201
- await browserPage.evaluate(scrollExpr);
202
- return `Scrolled ${direction}${amount ? ` ${amount}px` : ' one viewport'}`;
203
- }
204
- case 'evaluate': {
205
- if (!browserPage)
206
- return 'Error: Browser not open. Call open(url) first.';
207
- const script = input.script;
208
- if (!script)
209
- return 'Error: script is required for evaluate.';
210
- const result = await browserPage.evaluate(script);
211
- return JSON.stringify(result);
212
- }
213
- case 'gettext': {
214
- if (!browserPage)
215
- return 'Error: Browser not open. Call open(url) first.';
216
- const selector = input.selector;
217
- if (selector) {
218
- const text = await browserPage.textContent(selector, { timeout: timeoutMs });
219
- return text ?? '(no text content)';
220
- }
221
- const bodyText = await browserPage.evaluate('document.body.innerText');
222
- return bodyText || '(empty page)';
223
- }
224
- case 'screenshot': {
225
- if (!browserPage)
226
- return 'Error: Browser not open. Call open(url) first.';
227
- const filePath = resolveScreenshotPath(input.file_path, config);
228
- await browserPage.screenshot({ path: filePath, fullPage: true });
229
- return `Saved screenshot: ${filePath}`;
230
- }
231
- case 'wait': {
232
- const waitMs = typeof input.timeMs === 'number' ? input.timeMs : timeoutMs;
233
- await new Promise((r) => setTimeout(r, waitMs));
234
- return `Waited ${waitMs}ms`;
235
- }
236
- case 'close': {
237
- await cleanupBrowser();
238
- return 'Browser closed.';
239
- }
240
- default:
241
- return `Error: Unknown browser action "${action}"`;
242
- }
243
- }
244
- export async function cleanupBrowser() {
245
- if (browserPage) {
246
- await browserPage.close().catch(() => { });
247
- browserPage = null;
248
- }
249
- if (browserContext) {
250
- await browserContext.close().catch(() => { });
251
- browserContext = null;
252
- }
253
- browserOptionsKey = null;
254
- }
255
- // Prevent orphaned browser processes on exit
256
- const handleExit = () => {
257
- if (browserContext) {
258
- browserContext.close().catch(() => { });
259
- browserContext = null;
260
- browserPage = null;
261
- browserOptionsKey = null;
262
- }
263
- };
264
- process.on('SIGTERM', handleExit);
265
- process.on('SIGINT', handleExit);
266
- process.on('exit', handleExit);