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
@@ -5,7 +5,6 @@ const TOOL_NAME_MAP = {
5
5
  'Write': 'write_file',
6
6
  'Glob': 'list_directory',
7
7
  'Bash': 'bash',
8
- 'Browser': 'browser',
9
8
  };
10
9
  // Reverse map: internal name -> Claude Code name
11
10
  const INTERNAL_TO_CC = Object.fromEntries(Object.entries(TOOL_NAME_MAP).map(([cc, internal]) => [internal, cc]));
@@ -21,156 +20,104 @@ export function toClaudeCodeName(name) {
21
20
  export const BUILTIN_TOOL_DEFINITIONS = [
22
21
  {
23
22
  name: 'Read',
24
- description: 'Read the contents of a file at the given absolute path.',
25
- input_schema: {
26
- type: 'object',
27
- properties: {
28
- file_path: { type: 'string', description: 'Absolute path to the file to read' },
29
- },
30
- required: ['file_path'],
31
- },
23
+ input_schema: { type: 'object', properties: { path: {} } },
32
24
  },
33
25
  {
34
26
  name: 'Write',
35
- description: 'Write content to a file. Creates parent directories if needed. Overwrites existing files.',
36
- input_schema: {
37
- type: 'object',
38
- properties: {
39
- file_path: { type: 'string', description: 'Absolute path to the file to write' },
40
- content: { type: 'string', description: 'Content to write to the file' },
41
- },
42
- required: ['file_path', 'content'],
43
- },
27
+ input_schema: { type: 'object', properties: { path: {}, content: {} }, required: ['path', 'content'] },
44
28
  },
45
29
  {
46
30
  name: 'Glob',
47
- description: 'List files and directories at the given path. Returns name, type (file/dir), and size.',
48
- input_schema: {
49
- type: 'object',
50
- properties: {
51
- path: { type: 'string', description: 'Absolute path to the directory to list' },
52
- },
53
- required: ['path'],
54
- },
31
+ input_schema: { type: 'object', properties: { pattern: {} } },
55
32
  },
56
33
  {
57
34
  name: 'Bash',
58
- description: 'Execute a shell command and return stdout/stderr. Use for CLI tools like gh, icalBuddy, date, etc.',
59
- input_schema: {
60
- type: 'object',
61
- properties: {
62
- command: { type: 'string', description: 'Shell command to execute' },
63
- cwd: { type: 'string', description: 'Working directory (optional)' },
64
- },
65
- required: ['command'],
66
- },
35
+ input_schema: { type: 'object', properties: { cmd: {} } },
67
36
  },
68
37
  ];
69
- export const BROWSER_TOOL_DEFINITION = {
70
- name: 'Browser',
71
- description: `Control a browser via Playwright. Actions (case-insensitive):
72
- - open: Navigate to URL. Required: url
73
- - click: Click element. Required: selector
74
- - type: Fill input. Required: selector, text
75
- - select: Pick dropdown option. Required: selector, text (value)
76
- - hover: Hover element. Required: selector
77
- - scroll: Scroll page. Optional: selector (scrollIntoView), direction (up/down), amount (pixels)
78
- - waitFor: Wait for element/text. Required: selector OR text
79
- - evaluate: Run JavaScript in page. Required: script. Returns JSON result.
80
- - getText: Get visible text. Optional: selector (defaults to full page body text)
81
- - screenshot: Capture page. Optional: file_path
82
- - wait: Delay. Required: timeMs
83
- - close: Close browser`,
84
- input_schema: {
85
- type: 'object',
86
- properties: {
87
- action: { type: 'string', description: 'Action to perform (see description)' },
88
- type: { type: 'string', description: 'Browser type: chromium | firefox | webkit (optional, config default)' },
89
- url: { type: 'string', description: 'URL to open (open action)' },
90
- selector: { type: 'string', description: 'CSS selector (click/type/waitFor/getText/scroll/select/hover)' },
91
- text: { type: 'string', description: 'Text to type, wait for, or select value (type/waitFor/select)' },
92
- script: { type: 'string', description: 'JavaScript code to evaluate in page (evaluate action)' },
93
- direction: { type: 'string', description: 'Scroll direction: up or down (scroll action, default: down)' },
94
- amount: { type: 'number', description: 'Pixels to scroll (scroll action, default: one viewport height)' },
95
- file_path: { type: 'string', description: 'Absolute path to save screenshot (optional)' },
96
- timeoutMs: { type: 'number', description: 'Timeout in ms (optional)' },
97
- timeMs: { type: 'number', description: 'Time to wait in ms (wait action)' },
98
- headless: { type: 'boolean', description: 'Override headless for open (optional)' },
99
- slowMoMs: { type: 'number', description: 'Slow motion delay per action (ms) (optional)' },
100
- userAgent: { type: 'string', description: 'Override user agent (optional)' },
101
- viewport: {
102
- type: 'object',
103
- properties: {
104
- width: { type: 'number' },
105
- height: { type: 'number' },
106
- },
107
- },
108
- // executablePath and profileDir are config-only for security (no model overrides)
109
- },
110
- required: ['action'],
111
- },
112
- };
113
38
  export const FETCH_TOOL_DEFINITION = {
114
39
  name: 'Fetch',
115
- description: 'Make an HTTP request and return the response. HTML is auto-converted to plain text. Use for APIs, web search (e.g. https://duckduckgo.com/html/?q=your+query), or fetching page content. Prefer over Browser for simple requests.',
116
- input_schema: {
117
- type: 'object',
118
- properties: {
119
- url: { type: 'string', description: 'URL to fetch' },
120
- method: { type: 'string', description: 'HTTP method (GET, POST, PUT, DELETE, PATCH). Default: GET' },
121
- headers: {
122
- type: 'object',
123
- description: 'Request headers (e.g. {"Authorization": "Bearer ..."})',
124
- additionalProperties: { type: 'string' },
125
- },
126
- body: { type: 'string', description: 'Request body (for POST/PUT/PATCH)' },
127
- },
128
- required: ['url'],
129
- },
40
+ input_schema: { type: 'object', properties: { url: {} } },
130
41
  };
131
- // Legacy export for backward compat — static list (built-ins + browser + no MCP)
132
- export const TOOL_DEFINITIONS = [...BUILTIN_TOOL_DEFINITIONS, BROWSER_TOOL_DEFINITION, FETCH_TOOL_DEFINITION];
42
+ // Legacy export for backward compat — static list (built-ins + fetch + no MCP)
43
+ export const TOOL_DEFINITIONS = [...BUILTIN_TOOL_DEFINITIONS, FETCH_TOOL_DEFINITION];
133
44
  export const CODE_WITH_AGENT_TOOL = {
134
45
  name: 'code_with_agent',
135
- description: 'Delegate a coding task to a coding agent CLI (Claude Code or Codex). The agent will edit files, run commands, and return results. Always use this for code changes instead of writing code directly.',
136
- input_schema: {
137
- type: 'object',
138
- properties: {
139
- task: { type: 'string', description: 'Detailed coding task. Be specific: what to change, why, which files, expected behavior.' },
140
- agent: { type: 'string', enum: ['claude', 'codex', 'kimi'], description: 'Which coding CLI to use. Omit to use configured default.' },
141
- workdir: { type: 'string', description: 'Working directory (default: SkimpyClaw repo root)' },
142
- model: { type: 'string', description: 'Model override (e.g. opus, gpt-5.3-codex)' },
143
- max_turns: { type: 'number', description: 'Max agentic turns, Claude only (default: 30)' },
144
- timeout_minutes: { type: 'number', description: 'Timeout in minutes (default: 10, max: 30)' },
145
- validate: { type: 'boolean', description: 'Run build && test after completion using the auto-detected package manager (default: true)' },
146
- },
147
- required: ['task'],
148
- },
149
- };
150
- export const CODE_WITH_TEAM_TOOL = {
151
- name: 'code_with_team',
152
- description: 'Decompose a complex task into subtasks and run multiple code_with_agent instances in parallel. SkimpyClaw manages coordination: decomposes the task, spawns N parallel agents, monitors progress, synthesizes results, and validates. Use for multi-file refactors, cross-layer changes, or tasks with independent subtasks.',
153
46
  input_schema: {
154
47
  type: 'object',
155
48
  properties: {
156
- task: { type: 'string', description: 'Detailed task description. Be specific: what to change, why, which files, expected behavior.' },
157
- team_size: { type: 'number', description: 'Number of parallel agents (2-5, default 3)' },
158
- workdir: { type: 'string', description: 'Working directory or project name (default: SkimpyClaw repo root)' },
159
- agent: { type: 'string', enum: ['claude', 'codex', 'kimi'], description: 'Which coding CLI to use for all team workers. Omit to use configured default.' },
160
- model: { type: 'string', description: 'Model override (e.g. claude-sonnet-4-5, gpt-5.3-codex)' },
161
- timeout_minutes: { type: 'number', description: 'Total timeout in minutes (default: 20, max: 60)' },
162
- validate: { type: 'boolean', description: 'Run build && test after all agents complete using the auto-detected package manager (default: true)' },
49
+ task: { type: 'string' },
50
+ agent: { type: 'string' },
51
+ interactive: {
52
+ type: 'boolean',
53
+ description: 'If true, spawn as an interactive session. Creates a Discord thread and resumes session on follow-up messages. Discord-only; claude or codex only. Default false.',
54
+ },
55
+ effort: {
56
+ type: 'string',
57
+ enum: ['none', 'low', 'medium', 'high', 'xhigh'],
58
+ description: 'Optional reasoning effort for coding agents that support it.',
59
+ },
60
+ worktree: {
61
+ description: 'Use an isolated git worktree for this task. true forces one, false disables it, auto uses one for review/rebase-style tasks. Default auto.',
62
+ },
163
63
  },
164
64
  required: ['task'],
165
65
  },
166
66
  };
167
67
  export const CHECK_CODE_AGENT_TOOL = {
168
68
  name: 'check_code_agent',
169
- description: 'Check status of running coding agents. Call with no args to list all, or with id to get details.',
69
+ input_schema: { type: 'object', properties: { id: { type: 'string' } } },
70
+ };
71
+ export const DELEGATE_TO_AGENT_TOOL = {
72
+ name: 'delegate_to_agent',
170
73
  input_schema: {
171
74
  type: 'object',
172
75
  properties: {
173
- id: { type: 'string', description: 'Agent ID (e.g. ca-1). Omit to list all active agents.' },
76
+ alias: {
77
+ type: 'string',
78
+ description: 'Discord agent profile alias to delegate to, without @.',
79
+ },
80
+ task: {
81
+ type: 'string',
82
+ description: 'Task or question for the target agent profile.',
83
+ },
84
+ mode: {
85
+ type: 'string',
86
+ enum: ['new_thread'],
87
+ description: 'Delegation mode. Only new_thread is currently supported.',
88
+ },
89
+ wait: {
90
+ type: 'boolean',
91
+ description: 'If true, wait for the delegated agent response. Default false.',
92
+ },
174
93
  },
94
+ required: ['alias', 'task'],
175
95
  },
176
96
  };
97
+ // Glob tool reference for dynamic loading
98
+ const GLOB_TOOL = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'Glob');
99
+ // Write tool reference for dynamic loading
100
+ const WRITE_TOOL = BUILTIN_TOOL_DEFINITIONS.find(t => t.name === 'Write');
101
+ // Core tools — always sent. Extended tools added when conversation references them.
102
+ export const CORE_TOOL_DEFINITIONS = [...BUILTIN_TOOL_DEFINITIONS.filter(t => t.name !== 'Glob' && t.name !== 'Write')];
103
+ export const EXTENDED_TOOL_DEFINITIONS = [WRITE_TOOL, GLOB_TOOL, FETCH_TOOL_DEFINITION, CODE_WITH_AGENT_TOOL, CHECK_CODE_AGENT_TOOL, DELEGATE_TO_AGENT_TOOL];
104
+ // Keywords that trigger inclusion of extended tools
105
+ const TOOL_TRIGGERS = {
106
+ Write: ['write', 'create file', 'save', 'update file', 'fix', 'edit', 'modify', 'change'],
107
+ Glob: ['glob', 'list_directory', 'find files', 'directory listing'],
108
+ Fetch: ['fetch', 'http://', 'https://', 'curl', 'api call'],
109
+ code_with_agent: ['code_with_agent', 'delegate', 'subagent', 'sub-agent'],
110
+ check_code_agent: ['check_code_agent', 'agent status', 'agent_id'],
111
+ delegate_to_agent: ['delegate_to_agent', 'delegate to agent', 'call agent', 'ask agent', '@agent'],
112
+ };
113
+ /** Return tool defs filtered by what's been used/mentioned in conversation */
114
+ export function getActiveToolDefs(messages) {
115
+ const msgStr = JSON.stringify(messages).toLowerCase();
116
+ const extra = [];
117
+ for (const t of EXTENDED_TOOL_DEFINITIONS) {
118
+ const triggers = TOOL_TRIGGERS[t.name] || [t.name.toLowerCase()];
119
+ if (triggers.some(kw => msgStr.includes(kw)))
120
+ extra.push(t);
121
+ }
122
+ return [...CORE_TOOL_DEFINITIONS, ...extra];
123
+ }
@@ -1,8 +1,9 @@
1
+ import type { AbortSignalLike } from '../types.js';
1
2
  export interface ExecuteToolContext {
2
3
  /** Task ID for file lock acquisition (concurrent writes) */
3
4
  lockTaskId?: string;
4
5
  /** Abort signal for cancelling long-running tool loops */
5
- abortSignal?: AbortSignal;
6
+ abortSignal?: AbortSignalLike;
6
7
  /** Chat ID for channel routing */
7
8
  chatId?: number;
8
9
  /** Full config for agent tools */
@@ -25,8 +26,16 @@ export interface ExecuteToolContext {
25
26
  agentId?: string;
26
27
  /** True when this context is from a cron job — enables spawn tools even without a chatId */
27
28
  isCronJob?: boolean;
28
- /** Sandbox configuration for containerized tool execution */
29
- sandboxConfig?: import('../types.js').SandboxConfig;
30
- /** Session ID for sandbox container mapping */
29
+ /** Session ID used as file-lock scope + interactive-session key */
31
30
  sessionId?: string;
31
+ /** Discord thread ID — set when command originates from a thread */
32
+ discordThreadId?: string;
33
+ /** Discord channel ID — parent channel when originating from a thread */
34
+ discordChannelId?: string;
35
+ /** True when Discord message originated from a DM (threads not supported) */
36
+ isDm?: boolean;
37
+ /** Discord agent profile alias for the current thread/profile turn */
38
+ threadAgentAlias?: string;
39
+ /** Nested agent delegation depth */
40
+ delegationDepth?: number;
32
41
  }
@@ -1,6 +1,84 @@
1
1
  // Fetch tool — lightweight HTTP requests and web search without browser overhead
2
+ import { lookup } from 'dns/promises';
3
+ import { BlockList, isIP } from 'net';
2
4
  const MAX_RESPONSE_CHARS = 50_000;
3
5
  const TIMEOUT_MS = 30_000;
6
+ const MAX_REDIRECTS = 5;
7
+ const BLOCKED_HOSTNAMES = new Set([
8
+ 'localhost',
9
+ 'localhost.localdomain',
10
+ 'metadata',
11
+ 'metadata.google.internal',
12
+ 'metadata.google.internal.',
13
+ 'instance-data',
14
+ ]);
15
+ const BLOCKED_HOST_SUFFIXES = ['.internal', '.local', '.lan', '.home', '.localhost'];
16
+ const IP_BLOCKLIST = (() => {
17
+ const blockList = new BlockList();
18
+ blockList.addSubnet('0.0.0.0', 8, 'ipv4');
19
+ blockList.addSubnet('10.0.0.0', 8, 'ipv4');
20
+ blockList.addSubnet('100.64.0.0', 10, 'ipv4');
21
+ blockList.addSubnet('127.0.0.0', 8, 'ipv4');
22
+ blockList.addSubnet('169.254.0.0', 16, 'ipv4');
23
+ blockList.addSubnet('172.16.0.0', 12, 'ipv4');
24
+ blockList.addSubnet('192.0.0.0', 24, 'ipv4');
25
+ blockList.addSubnet('192.0.2.0', 24, 'ipv4');
26
+ blockList.addSubnet('192.168.0.0', 16, 'ipv4');
27
+ blockList.addSubnet('198.18.0.0', 15, 'ipv4');
28
+ blockList.addSubnet('198.51.100.0', 24, 'ipv4');
29
+ blockList.addSubnet('203.0.113.0', 24, 'ipv4');
30
+ blockList.addSubnet('224.0.0.0', 4, 'ipv4');
31
+ blockList.addSubnet('240.0.0.0', 4, 'ipv4');
32
+ blockList.addAddress('::', 'ipv6');
33
+ blockList.addAddress('::1', 'ipv6');
34
+ blockList.addSubnet('fc00::', 7, 'ipv6');
35
+ blockList.addSubnet('fe80::', 10, 'ipv6');
36
+ blockList.addSubnet('ff00::', 8, 'ipv6');
37
+ blockList.addSubnet('2001:db8::', 32, 'ipv6');
38
+ return blockList;
39
+ })();
40
+ function isBlockedIpAddress(address) {
41
+ if (address.startsWith('::ffff:')) {
42
+ return isBlockedIpAddress(address.slice('::ffff:'.length));
43
+ }
44
+ const family = isIP(address);
45
+ if (family === 4)
46
+ return IP_BLOCKLIST.check(address, 'ipv4');
47
+ if (family === 6)
48
+ return IP_BLOCKLIST.check(address, 'ipv6');
49
+ return true;
50
+ }
51
+ async function validateTarget(rawUrl) {
52
+ const parsed = new URL(rawUrl);
53
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
54
+ throw new Error(`Unsupported protocol: ${parsed.protocol}`);
55
+ }
56
+ const hostname = parsed.hostname.toLowerCase().replace(/\.$/, '');
57
+ if (BLOCKED_HOSTNAMES.has(hostname)) {
58
+ throw new Error(`Blocked host: ${hostname}`);
59
+ }
60
+ if (BLOCKED_HOST_SUFFIXES.some(suffix => hostname.endsWith(suffix))) {
61
+ throw new Error(`Blocked internal host: ${hostname}`);
62
+ }
63
+ if (isIP(hostname) !== 0 && isBlockedIpAddress(hostname)) {
64
+ throw new Error(`Blocked target IP: ${hostname}`);
65
+ }
66
+ if (isIP(hostname) === 0) {
67
+ const resolved = await lookup(hostname, { all: true, verbatim: true });
68
+ if (!resolved.length) {
69
+ throw new Error(`Could not resolve host: ${hostname}`);
70
+ }
71
+ for (const rec of resolved) {
72
+ if (isBlockedIpAddress(rec.address)) {
73
+ throw new Error(`Blocked resolved IP for ${hostname}: ${rec.address}`);
74
+ }
75
+ }
76
+ }
77
+ return parsed;
78
+ }
79
+ function isRedirectStatus(status) {
80
+ return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
81
+ }
4
82
  /**
5
83
  * Strip HTML tags and decode common entities. Returns plain text.
6
84
  */
@@ -31,22 +109,40 @@ export async function executeFetch(input, _config) {
31
109
  if (!url)
32
110
  return 'Error: url is required';
33
111
  try {
34
- new URL(url);
35
- }
36
- catch {
37
- return `Error: Invalid URL: ${url}`;
38
- }
39
- try {
112
+ let target = await validateTarget(url);
40
113
  const defaultHeaders = {
41
114
  'User-Agent': 'SkimpyClaw/1.0',
42
115
  };
43
- const response = await fetch(url, {
44
- method: method.toUpperCase(),
45
- headers: { ...defaultHeaders, ...headers },
46
- body: body || undefined,
47
- signal: AbortSignal.timeout(TIMEOUT_MS),
48
- redirect: 'follow',
49
- });
116
+ let requestMethod = method.toUpperCase();
117
+ let requestBody = body || undefined;
118
+ let response = null;
119
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
120
+ response = await fetch(target, {
121
+ method: requestMethod,
122
+ headers: { ...defaultHeaders, ...headers },
123
+ body: requestBody,
124
+ signal: AbortSignal.timeout(TIMEOUT_MS),
125
+ redirect: 'manual',
126
+ });
127
+ if (!isRedirectStatus(response.status)) {
128
+ break;
129
+ }
130
+ if (hop === MAX_REDIRECTS) {
131
+ return `Error: Too many redirects (>${MAX_REDIRECTS})`;
132
+ }
133
+ const location = response.headers.get('location');
134
+ if (!location) {
135
+ return `Error: Redirect response missing Location header (HTTP ${response.status})`;
136
+ }
137
+ target = await validateTarget(new URL(location, target).toString());
138
+ if ((response.status === 301 || response.status === 302 || response.status === 303) && requestMethod !== 'GET' && requestMethod !== 'HEAD') {
139
+ requestMethod = 'GET';
140
+ requestBody = undefined;
141
+ }
142
+ }
143
+ if (!response) {
144
+ return 'Error: Request did not produce a response';
145
+ }
50
146
  const status = `${response.status} ${response.statusText}`;
51
147
  const contentType = response.headers.get('content-type') || '';
52
148
  let responseBody;
@@ -1,7 +1,13 @@
1
1
  import { readFileSync, writeFileSync, readdirSync, existsSync, statSync, mkdirSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
+ import { homedir } from 'os';
3
4
  import { isPathAllowed } from './path-utils.js';
5
+ /** Expand ~ to home directory */
6
+ function expandTilde(p) {
7
+ return p.startsWith('~/') ? join(homedir(), p.slice(2)) : p;
8
+ }
4
9
  export function executeReadFile(path, config) {
10
+ path = expandTilde(path);
5
11
  if (!isPathAllowed(path, config.allowedPaths)) {
6
12
  return `Error: Path not allowed. Permitted: ${config.allowedPaths.join(', ')}`;
7
13
  }
@@ -23,7 +29,7 @@ function executeWriteFile(path, content, config) {
23
29
  mkdirSync(dir, { recursive: true });
24
30
  }
25
31
  writeFileSync(path, content, 'utf-8');
26
- return `Written: ${path} (${content.length} bytes)`;
32
+ return `OK`;
27
33
  }
28
34
  /**
29
35
  * Write with file locking when a lockTaskId is provided (concurrent context).
package/dist/tools.d.ts CHANGED
@@ -1,19 +1,18 @@
1
1
  import type { ToolConfig } from './types.js';
2
- import { fromClaudeCodeName, toClaudeCodeName, BUILTIN_TOOL_DEFINITIONS, BROWSER_TOOL_DEFINITION, FETCH_TOOL_DEFINITION, TOOL_DEFINITIONS, CODE_WITH_AGENT_TOOL, CODE_WITH_TEAM_TOOL, CHECK_CODE_AGENT_TOOL } from './tools/definitions.js';
2
+ import { fromClaudeCodeName, toClaudeCodeName, BUILTIN_TOOL_DEFINITIONS, FETCH_TOOL_DEFINITION, TOOL_DEFINITIONS, CODE_WITH_AGENT_TOOL, CHECK_CODE_AGENT_TOOL, DELEGATE_TO_AGENT_TOOL } from './tools/definitions.js';
3
3
  import type { ExecuteToolContext } from './tools/execute-context.js';
4
- import { cleanupBrowser } from './tools/browser-tool.js';
5
- export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgent, cancelCodeAgent, restoreCodeAgentTasks, setCodeAgentConfig, computeWaves, decomposeTask, synthesizeResults, runValidation, runCodeAgentBackground, buildCodeAgentArgs, readTeamState, parseStreamJsonForLive, } from './code-agents/index.js';
6
- export { fromClaudeCodeName, toClaudeCodeName, BUILTIN_TOOL_DEFINITIONS, BROWSER_TOOL_DEFINITION, FETCH_TOOL_DEFINITION, TOOL_DEFINITIONS, CODE_WITH_AGENT_TOOL, CODE_WITH_TEAM_TOOL, CHECK_CODE_AGENT_TOOL, cleanupBrowser, };
4
+ export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgent, cancelCodeAgent, restoreCodeAgentTasks, setCodeAgentConfig, runValidation, runCodeAgentBackground, buildCodeAgentArgs, parseStreamJsonForLive, } from './code-agents/index.js';
5
+ export { fromClaudeCodeName, toClaudeCodeName, BUILTIN_TOOL_DEFINITIONS, FETCH_TOOL_DEFINITION, TOOL_DEFINITIONS, CODE_WITH_AGENT_TOOL, CHECK_CODE_AGENT_TOOL, DELEGATE_TO_AGENT_TOOL, };
7
6
  export type { ExecuteToolContext };
8
- export type { CodeAgentTask, DecomposedSubtask } from './code-agents/index.js';
7
+ export type { CodeAgentTask } from './code-agents/index.js';
9
8
  export declare function discoverMcpTools(): Promise<any[]>;
10
9
  export declare function clearMcpToolCache(): void;
11
10
  /** Force-reconnect the MCP runtime (clears cached runtime and tool discovery). */
12
11
  export declare function reconnectMcp(): Promise<void>;
13
12
  /**
14
- * Get all available tool definitions: built-ins + browser (if enabled) + MCP (auto-discovered) + agent tools.
13
+ * Get all available tool definitions: built-ins + fetch + MCP (auto-discovered) + agent tools.
15
14
  * This is the primary way to get tools — replaces the static TOOL_DEFINITIONS export.
16
- * Pass includeAgentTools: true to include code_with_agent, code_with_team, check_code_agent.
15
+ * Pass includeAgentTools: true to include code_with_agent and check_code_agent.
17
16
  * Results are cached for 60s to avoid rebuilding the array on every agent turn.
18
17
  */
19
18
  export declare function getToolDefinitions(config?: ToolConfig, options?: {
@@ -22,5 +21,6 @@ export declare function getToolDefinitions(config?: ToolConfig, options?: {
22
21
  projects?: Record<string, string>;
23
22
  }): Promise<any[]>;
24
23
  export declare function clearToolDefsCache(): void;
24
+ export declare function normalizeMcpToolArgsForExecution(server: string, tool: string, args: Record<string, any>): Record<string, any>;
25
25
  export declare function cleanupMcp(): Promise<void>;
26
26
  export declare function executeTool(name: string, input: Record<string, any>, config: ToolConfig, context?: ExecuteToolContext): Promise<string>;