skimpyclaw 0.3.15 → 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.
- package/README.md +47 -37
- package/dist/__tests__/adapter-types.test.d.ts +4 -0
- package/dist/__tests__/adapter-types.test.js +63 -0
- package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
- package/dist/__tests__/anthropic-adapter.test.js +264 -0
- package/dist/__tests__/api.test.js +0 -1
- package/dist/__tests__/cli.integration.test.js +2 -4
- package/dist/__tests__/cli.test.js +0 -1
- package/dist/__tests__/code-agents-notifications.test.js +137 -0
- package/dist/__tests__/code-agents-parser.test.js +19 -1
- package/dist/__tests__/code-agents-preflight.test.js +3 -28
- package/dist/__tests__/code-agents-utils.test.js +34 -9
- package/dist/__tests__/code-agents-worktrees.test.js +116 -0
- package/dist/__tests__/codex-adapter.test.js +184 -0
- package/dist/__tests__/codex-auth.test.js +66 -0
- package/dist/__tests__/codex-provider-gating.test.js +35 -0
- package/dist/__tests__/codex-unified-loop.test.js +111 -0
- package/dist/__tests__/config-security.test.js +127 -0
- package/dist/__tests__/config.test.js +23 -0
- package/dist/__tests__/context-manager.test.js +243 -164
- package/dist/__tests__/cron-run.test.js +250 -0
- package/dist/__tests__/cron.test.js +12 -38
- package/dist/__tests__/digests.test.js +67 -0
- package/dist/__tests__/discord-attachments.test.js +211 -0
- package/dist/__tests__/discord-docs.test.d.ts +1 -0
- package/dist/__tests__/discord-docs.test.js +27 -0
- package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-agents.test.js +115 -0
- package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-context.test.js +42 -0
- package/dist/__tests__/doctor.formatters.test.js +4 -4
- package/dist/__tests__/doctor.index.test.js +1 -1
- package/dist/__tests__/doctor.runner.test.js +3 -15
- package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
- package/dist/__tests__/env-sanitizer.test.js +45 -0
- package/dist/__tests__/exec-approval.test.js +61 -0
- package/dist/__tests__/fetch-tool.test.d.ts +1 -0
- package/dist/__tests__/fetch-tool.test.js +85 -0
- package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
- package/dist/__tests__/gateway-status-auth.test.js +72 -0
- package/dist/__tests__/heartbeat.test.js +3 -3
- package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
- package/dist/__tests__/interactive-sessions.test.js +96 -0
- package/dist/__tests__/langfuse.test.js +6 -18
- package/dist/__tests__/model-selection.test.js +3 -4
- package/dist/__tests__/providers-init.test.js +2 -8
- package/dist/__tests__/providers-routing.test.js +1 -1
- package/dist/__tests__/providers-utils.test.js +13 -3
- package/dist/__tests__/sessions.test.js +14 -10
- package/dist/__tests__/setup.test.js +12 -29
- package/dist/__tests__/skills.test.js +10 -7
- package/dist/__tests__/stream-formatter.test.d.ts +1 -0
- package/dist/__tests__/stream-formatter.test.js +114 -0
- package/dist/__tests__/token-efficiency.test.js +131 -15
- package/dist/__tests__/tool-loop.test.d.ts +4 -0
- package/dist/__tests__/tool-loop.test.js +505 -0
- package/dist/__tests__/tools.test.js +100 -276
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +14 -0
- package/dist/__tests__/voice.test.js +21 -0
- package/dist/agent.js +35 -4
- package/dist/api.js +113 -37
- package/dist/channels/discord/attachments.d.ts +50 -0
- package/dist/channels/discord/attachments.js +137 -0
- package/dist/channels/discord/delegation.d.ts +5 -0
- package/dist/channels/discord/delegation.js +136 -0
- package/dist/channels/discord/handlers.js +694 -7
- package/dist/channels/discord/index.d.ts +16 -1
- package/dist/channels/discord/index.js +64 -1
- package/dist/channels/discord/thread-agents.d.ts +54 -0
- package/dist/channels/discord/thread-agents.js +323 -0
- package/dist/channels/discord/threads.d.ts +58 -0
- package/dist/channels/discord/threads.js +192 -0
- package/dist/channels/discord/types.js +4 -2
- package/dist/channels/discord/utils.d.ts +16 -0
- package/dist/channels/discord/utils.js +86 -6
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/types.js +1 -1
- package/dist/channels/telegram/utils.js +9 -3
- package/dist/channels.d.ts +1 -1
- package/dist/cli.js +20 -400
- package/dist/code-agents/executor.d.ts +1 -1
- package/dist/code-agents/executor.js +101 -45
- package/dist/code-agents/index.d.ts +2 -7
- package/dist/code-agents/index.js +111 -80
- package/dist/code-agents/interactive-resume.d.ts +6 -0
- package/dist/code-agents/interactive-resume.js +98 -0
- package/dist/code-agents/interactive-sessions.d.ts +20 -0
- package/dist/code-agents/interactive-sessions.js +132 -0
- package/dist/code-agents/parser.js +5 -1
- package/dist/code-agents/registry.d.ts +7 -1
- package/dist/code-agents/registry.js +11 -23
- package/dist/code-agents/stream-formatter.d.ts +8 -0
- package/dist/code-agents/stream-formatter.js +92 -0
- package/dist/code-agents/types.d.ts +16 -24
- package/dist/code-agents/utils.d.ts +35 -11
- package/dist/code-agents/utils.js +348 -95
- package/dist/code-agents/worktrees.d.ts +37 -0
- package/dist/code-agents/worktrees.js +116 -0
- package/dist/config.d.ts +2 -4
- package/dist/config.js +123 -23
- package/dist/cron.d.ts +1 -6
- package/dist/cron.js +175 -82
- package/dist/dashboard/assets/index-B345aOO-.js +65 -0
- package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/digests.d.ts +1 -0
- package/dist/digests.js +132 -42
- package/dist/doctor/checks.d.ts +0 -3
- package/dist/doctor/checks.js +1 -108
- package/dist/doctor/runner.js +1 -4
- package/dist/env-sanitizer.d.ts +2 -0
- package/dist/env-sanitizer.js +61 -0
- package/dist/exec-approval.d.ts +11 -1
- package/dist/exec-approval.js +17 -4
- package/dist/gateway.d.ts +3 -1
- package/dist/gateway.js +17 -7
- package/dist/heartbeat.js +1 -6
- package/dist/langfuse.js +3 -29
- package/dist/model-selection.js +3 -1
- package/dist/providers/adapter.d.ts +118 -0
- package/dist/providers/adapter.js +6 -0
- package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
- package/dist/providers/adapters/anthropic-adapter.js +204 -0
- package/dist/providers/adapters/codex-adapter.d.ts +26 -0
- package/dist/providers/adapters/codex-adapter.js +203 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -272
- package/dist/providers/codex.d.ts +21 -0
- package/dist/providers/codex.js +149 -330
- package/dist/providers/content.d.ts +1 -1
- package/dist/providers/content.js +2 -2
- package/dist/providers/context-manager.d.ts +18 -6
- package/dist/providers/context-manager.js +199 -223
- package/dist/providers/index.d.ts +9 -1
- package/dist/providers/index.js +73 -64
- package/dist/providers/loop-utils.d.ts +20 -0
- package/dist/providers/loop-utils.js +30 -0
- package/dist/providers/tool-loop.d.ts +12 -0
- package/dist/providers/tool-loop.js +251 -0
- package/dist/providers/utils.d.ts +19 -3
- package/dist/providers/utils.js +100 -29
- package/dist/secure-store.d.ts +8 -0
- package/dist/secure-store.js +80 -0
- package/dist/service.js +3 -28
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.js +147 -18
- package/dist/setup-templates.js +13 -25
- package/dist/setup.d.ts +10 -6
- package/dist/setup.js +84 -292
- package/dist/skills.js +3 -11
- package/dist/tools/agent-delegation.d.ts +19 -0
- package/dist/tools/agent-delegation.js +49 -0
- package/dist/tools/bash-tool.js +89 -34
- package/dist/tools/definitions.d.ts +199 -302
- package/dist/tools/definitions.js +70 -123
- package/dist/tools/execute-context.d.ts +13 -4
- package/dist/tools/fetch-tool.js +109 -13
- package/dist/tools/file-tools.js +7 -1
- package/dist/tools.d.ts +7 -7
- package/dist/tools.js +133 -151
- package/dist/types.d.ts +37 -30
- package/dist/utils.js +4 -6
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +17 -4
- package/package.json +33 -23
- package/templates/TOOLS.md +0 -27
- package/dist/__tests__/audit.test.js +0 -122
- package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
- package/dist/__tests__/code-agents-sandbox.test.js +0 -163
- package/dist/__tests__/orchestrator.test.js +0 -425
- package/dist/__tests__/sandbox-bridge.test.js +0 -116
- package/dist/__tests__/sandbox-manager.test.js +0 -144
- package/dist/__tests__/sandbox-mount-security.test.js +0 -139
- package/dist/__tests__/sandbox-runtime.test.js +0 -176
- package/dist/__tests__/subagent.test.js +0 -240
- package/dist/__tests__/telegram.test.js +0 -42
- package/dist/code-agents/orchestrator.d.ts +0 -29
- package/dist/code-agents/orchestrator.js +0 -694
- package/dist/code-agents/worktree.d.ts +0 -40
- package/dist/code-agents/worktree.js +0 -215
- package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
- package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
- package/dist/dashboard.d.ts +0 -8
- package/dist/dashboard.js +0 -4071
- package/dist/discord.d.ts +0 -8
- package/dist/discord.js +0 -792
- package/dist/mcp-context-a8c.d.ts +0 -13
- package/dist/mcp-context-a8c.js +0 -34
- package/dist/orchestrator.d.ts +0 -15
- package/dist/orchestrator.js +0 -676
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +0 -355
- package/dist/sandbox/bridge.d.ts +0 -5
- package/dist/sandbox/bridge.js +0 -63
- package/dist/sandbox/index.d.ts +0 -5
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/manager.d.ts +0 -7
- package/dist/sandbox/manager.js +0 -100
- package/dist/sandbox/mount-security.d.ts +0 -12
- package/dist/sandbox/mount-security.js +0 -122
- package/dist/sandbox/runtime.d.ts +0 -39
- package/dist/sandbox/runtime.js +0 -192
- package/dist/sandbox-utils.d.ts +0 -6
- package/dist/sandbox-utils.js +0 -36
- package/dist/subagent.d.ts +0 -19
- package/dist/subagent.js +0 -407
- package/dist/telegram.d.ts +0 -2
- package/dist/telegram.js +0 -11
- package/dist/tools/browser-tool.d.ts +0 -3
- package/dist/tools/browser-tool.js +0 -266
- package/sandbox/Dockerfile +0 -40
- /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
- /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
- /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
- /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# SkimpyClaw 👙🦞
|
|
2
2
|
|
|
3
|
-
Lightweight personal AI assistant (~
|
|
3
|
+
Lightweight personal AI assistant (~21k LOC). Runs locally. Telegram/Discord chat, scheduled routines, a web dashboard, and a tool-enabled agent — all in one tiny service.
|
|
4
4
|
|
|
5
5
|
## Why SkimpyClaw vs OpenClaw
|
|
6
6
|
|
|
7
7
|
Both are personal AI assistants you run yourself. The difference is scope.
|
|
8
8
|
|
|
9
|
-
| | SkimpyClaw (~
|
|
9
|
+
| | SkimpyClaw (~21k LOC) | OpenClaw (~700k LOC) |
|
|
10
10
|
| ------------------- | --------------------------------------- | -------------------------------------------------------- |
|
|
11
11
|
| **Channels** | Telegram, Discord | WhatsApp, Signal, iMessage, Slack, Teams, Matrix, + more |
|
|
12
12
|
| **Setup** | `skimpyclaw onboard` → done | Daemon + wizard + per-channel pairing |
|
|
13
13
|
| **Codebase** | Read it in an afternoon | Full platform with extensions, packages, native UI |
|
|
14
|
-
| **Model support** | Anthropic,
|
|
14
|
+
| **Model support** | Anthropic, Codex | Same + more |
|
|
15
15
|
| **Release cadence** | Move fast, no stability guarantees | Stable / beta / dev channels |
|
|
16
16
|
|
|
17
17
|
Use SkimpyClaw if you live in Telegram or Discord, want to read and own every line, and don't need 13 channels. Use OpenClaw if you need to support multiple channels like WhatsApp, Signal, iMessage, or Slack — or want a more polished, maintained platform.
|
|
@@ -19,10 +19,10 @@ Use SkimpyClaw if you live in Telegram or Discord, want to read and own every li
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
21
21
|
- **Chat interface** — Telegram and Discord bots with persistent conversation history
|
|
22
|
-
- **Tool-enabled agent** — file read/write, bash,
|
|
22
|
+
- **Tool-enabled agent** — file read/write, bash, fetch, MCP tools via mcporter
|
|
23
23
|
- **Multi-modal support** — voice messages (STT/TTS), image analysis
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
24
|
+
- **Agents** — configure multiple local agents with their own identity, prompts, model, effort, and memory; Discord adds reusable `/agent` profiles and `@alias <message>` shortcuts
|
|
25
|
+
- **Coding agents** — delegate coding tasks to Claude Code or Codex with `code_with_agent`; Discord threads can also run bidirectional interactive Claude sessions that `--resume` the same process
|
|
26
26
|
- **Cron scheduler** — run agent prompts or shell scripts on a schedule
|
|
27
27
|
- **Web dashboard** — Preact/Vite SPA with status, cron, audit log, memory, templates, config editor, skills, approvals
|
|
28
28
|
- **Heartbeat** — periodic keep-alive with Telegram/Discord alerts
|
|
@@ -31,15 +31,21 @@ Use SkimpyClaw if you live in Telegram or Discord, want to read and own every li
|
|
|
31
31
|
- **Voice** — optional TTS/STT support (ElevenLabs, OpenAI, macOS `say`, local Whisper)
|
|
32
32
|
- **Observability** — optional Langfuse tracing per agent turn
|
|
33
33
|
- **Audit logging** — JSONL-based audit traces for all agent interactions
|
|
34
|
-
- **
|
|
34
|
+
- **Model providers** — Anthropic and Codex (ChatGPT backend)
|
|
35
35
|
|
|
36
36
|
## Architecture
|
|
37
37
|
|
|
38
|
+
SkimpyClaw routes all model calls through a provider adapter registry in `src/providers/index.ts`.
|
|
39
|
+
|
|
40
|
+
- `chat()` and `chatWithTools()` both resolve provider/model and dispatch to a `ProviderAdapter`.
|
|
41
|
+
- Adapters implement `isAvailable()`, `chat()`, and `chatWithTools()` behavior via a shared interface.
|
|
42
|
+
- Tool-enabled turns use one common execution path: `runToolLoop()` in `src/providers/tool-loop.ts`.
|
|
43
|
+
- Context compaction is unified through shared context-management utilities used by all adapters.
|
|
44
|
+
|
|
38
45
|
```mermaid
|
|
39
46
|
flowchart LR
|
|
40
47
|
subgraph Channels
|
|
41
48
|
user["Telegram / Discord"]
|
|
42
|
-
browser["Browser"]
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
subgraph Gateway["Fastify :18790"]
|
|
@@ -49,6 +55,9 @@ flowchart LR
|
|
|
49
55
|
|
|
50
56
|
subgraph Core
|
|
51
57
|
agent["Agent Runtime"]
|
|
58
|
+
registry["Provider Registry"]
|
|
59
|
+
adapters["Provider Adapters"]
|
|
60
|
+
loop["runToolLoop()"]
|
|
52
61
|
codeAgents["Coding Agents"]
|
|
53
62
|
cron["Cron"]
|
|
54
63
|
hb["Heartbeat"]
|
|
@@ -58,19 +67,21 @@ flowchart LR
|
|
|
58
67
|
end
|
|
59
68
|
|
|
60
69
|
user --> agent
|
|
61
|
-
|
|
70
|
+
dash --> api --> agent
|
|
62
71
|
cron --> agent
|
|
63
72
|
hb --> agent
|
|
64
73
|
agent --> codeAgents
|
|
65
74
|
agent --> audit
|
|
66
75
|
agent --> skills
|
|
67
76
|
agent --> approvals
|
|
68
|
-
agent -->
|
|
77
|
+
agent --> registry --> adapters
|
|
78
|
+
adapters --> loop
|
|
79
|
+
adapters --> models["Anthropic / Codex"]
|
|
69
80
|
agent --> mcp["MCP Servers"]
|
|
70
81
|
agent --> fs["~/.skimpyclaw"]
|
|
71
82
|
```
|
|
72
83
|
|
|
73
|
-
See [docs/architecture.md](docs/architecture.md) for runtime flow and startup sequence diagrams.
|
|
84
|
+
See [docs/guide/architecture.md](docs/guide/architecture.md) for runtime flow and startup sequence diagrams.
|
|
74
85
|
|
|
75
86
|
## Quick Start
|
|
76
87
|
|
|
@@ -85,16 +96,6 @@ Onboarding validates your Telegram token, provider auth, and creates:
|
|
|
85
96
|
- `~/.skimpyclaw/config.json`
|
|
86
97
|
- `~/.skimpyclaw/agents/main/*.md` (from templates)
|
|
87
98
|
|
|
88
|
-
**Sandbox setup (optional — isolates Bash commands in a container):**
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
skimpyclaw sandbox init # detect runtime, build image, update config
|
|
92
|
-
skimpyclaw sandbox doctor # verify everything works
|
|
93
|
-
skimpyclaw restart # pick up sandbox config
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Requires Apple Containers (macOS 26+) or Docker. See [docs/guide/sandbox.md](docs/guide/sandbox.md) for options.
|
|
97
|
-
|
|
98
99
|
**Stop/Restart daemon (macOS):**
|
|
99
100
|
|
|
100
101
|
```bash
|
|
@@ -124,13 +125,18 @@ http://127.0.0.1:18790/dashboard
|
|
|
124
125
|
|
|
125
126
|
Bearer token is shown in startup logs.
|
|
126
127
|
|
|
128
|
+
## Security
|
|
129
|
+
|
|
130
|
+
SkimpyClaw uses defense-in-depth for local execution: strict config/secrets handling, bearer-authenticated control endpoints, constrained tool paths, SSRF protections, and safer command execution defaults.
|
|
131
|
+
|
|
132
|
+
For the full security model and controls, see [docs/guide/security.md](docs/guide/security.md).
|
|
133
|
+
|
|
127
134
|
## Tech Stack
|
|
128
135
|
|
|
129
136
|
- **Backend:** TypeScript (ESM), Fastify, Vitest
|
|
130
137
|
- **Frontend:** Preact, Vite, TypeScript
|
|
131
138
|
- **Chat:** grammy (Telegram), discord.js (Discord)
|
|
132
139
|
- **Scheduling:** Croner
|
|
133
|
-
- **Browser:** Playwright
|
|
134
140
|
- **AI SDKs:** Anthropic SDK, OpenAI SDK
|
|
135
141
|
- **Observability:** Langfuse (optional)
|
|
136
142
|
|
|
@@ -140,12 +146,12 @@ Bearer token is shown in startup logs.
|
|
|
140
146
|
src/
|
|
141
147
|
index.ts # App entrypoint with logging setup
|
|
142
148
|
gateway.ts # Fastify server + top-level routes
|
|
143
|
-
agent.ts # Prompt assembly,
|
|
149
|
+
agent.ts # Prompt assembly, runAgentTurn orchestration, memory writes
|
|
144
150
|
tools.ts # Tool registry + dispatch
|
|
145
|
-
tools/ # Tool executors (bash,
|
|
146
|
-
providers/ # Provider
|
|
147
|
-
code-agents/ # Background coding-agent runtime (executor/parser/
|
|
148
|
-
channels/ # Channel adapters/utilities (telegram/discord)
|
|
151
|
+
tools/ # Tool executors (bash, fetch, file tools, path utils, execute context)
|
|
152
|
+
providers/ # Provider registry, adapters, unified tool loop, provider implementations
|
|
153
|
+
code-agents/ # Background coding-agent runtime (executor/parser/registry + interactive sessions)
|
|
154
|
+
channels/ # Channel adapters/utilities (telegram/discord, Discord agent profile routing)
|
|
149
155
|
file-lock.ts # In-memory file lock for concurrent writes
|
|
150
156
|
audit.ts # Append-only audit log (trace/event model, JSONL storage)
|
|
151
157
|
cron.ts # Job scheduling + execution + cron logging
|
|
@@ -202,17 +208,21 @@ dist/ # Compiled output + built dashboard assets
|
|
|
202
208
|
|
|
203
209
|
| Doc | Contents |
|
|
204
210
|
| ---------------------------------------------- | ---------------------------------------------------------------- |
|
|
205
|
-
| [docs/architecture.md](docs/architecture.md) | Component diagram, runtime flow, startup sequence, source layout |
|
|
206
|
-
| [docs/configuration.md](docs/configuration.md) | Full config reference, all sections with examples |
|
|
207
|
-
| [docs/
|
|
208
|
-
| [docs/
|
|
209
|
-
| [docs/
|
|
210
|
-
| [docs/
|
|
211
|
-
| [docs/
|
|
212
|
-
| [docs/
|
|
213
|
-
| [docs/
|
|
211
|
+
| [docs/guide/architecture.md](docs/guide/architecture.md) | Component diagram, runtime flow, startup sequence, source layout |
|
|
212
|
+
| [docs/guide/configuration.md](docs/guide/configuration.md) | Full config reference, all sections with examples |
|
|
213
|
+
| [docs/guide/security.md](docs/guide/security.md) | Security model and runtime safeguards |
|
|
214
|
+
| [docs/guide/tools.md](docs/guide/tools.md) | Built-in tools, fetch, MCP integration, code agents |
|
|
215
|
+
| [docs/guide/dashboard.md](docs/guide/dashboard.md) | Web dashboard, all HTTP endpoints + API routes |
|
|
216
|
+
| [docs/guide/agents.md](docs/guide/agents.md) | Core agents, prompt directories, Discord profile aliases |
|
|
217
|
+
| [docs/guide/coding-agents.md](docs/guide/coding-agents.md) | Coding-agent execution model and CLI backends |
|
|
218
|
+
| [docs/guide/cli.md](docs/guide/cli.md) | CLI commands, service management |
|
|
219
|
+
| [docs/guide/chat-commands.md](docs/guide/chat-commands.md) | Telegram/Discord bot commands |
|
|
220
|
+
| [docs/guide/discord-updates.md](docs/guide/discord-updates.md) | Discord workflow documentation maintenance process |
|
|
221
|
+
| [docs/guide/changelog.md](docs/guide/changelog.md) | Documentation and behavior changelog |
|
|
222
|
+
| [docs/guide/skills.md](docs/guide/skills.md) | Skills system, built-in skills, creating custom skills |
|
|
223
|
+
| [docs/guide/data-storage.md](docs/guide/data-storage.md) | File layout, audit log format, security notes |
|
|
214
224
|
| [Setup Guide](https://docs.skimpyclaw.xyz/guide/setup-guide.html) | Step-by-step installation and setup guide |
|
|
215
|
-
| [docs/troubleshooting.md](docs/troubleshooting.md) | Common issues and solutions |
|
|
225
|
+
| [docs/guide/troubleshooting.md](docs/guide/troubleshooting.md) | Common issues and solutions |
|
|
216
226
|
|
|
217
227
|
## Development
|
|
218
228
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time validation tests for adapter interfaces.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import { buildToolLogEntry, logIteration } from '../providers/loop-utils.js';
|
|
6
|
+
describe('ProviderAdapter interface', () => {
|
|
7
|
+
it('should define correct structure for NormalizedResponse', () => {
|
|
8
|
+
const response = {
|
|
9
|
+
hasToolCalls: false,
|
|
10
|
+
toolCalls: [],
|
|
11
|
+
textContent: 'test response',
|
|
12
|
+
usage: { inputTokens: 100, outputTokens: 50 },
|
|
13
|
+
rawResponse: {},
|
|
14
|
+
};
|
|
15
|
+
expect(response.hasToolCalls).toBe(false);
|
|
16
|
+
expect(response.textContent).toBe('test response');
|
|
17
|
+
});
|
|
18
|
+
it('should define correct structure for NormalizedToolCall', () => {
|
|
19
|
+
const toolCall = {
|
|
20
|
+
id: 'call_123',
|
|
21
|
+
name: 'testTool',
|
|
22
|
+
args: { param: 'value' },
|
|
23
|
+
rawArgs: '{"param":"value"}',
|
|
24
|
+
};
|
|
25
|
+
expect(toolCall.name).toBe('testTool');
|
|
26
|
+
expect(toolCall.args.param).toBe('value');
|
|
27
|
+
});
|
|
28
|
+
it('should define correct structure for ProviderMessages', () => {
|
|
29
|
+
const messages = {
|
|
30
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
31
|
+
systemParam: 'system prompt',
|
|
32
|
+
};
|
|
33
|
+
expect(messages.messages).toHaveLength(1);
|
|
34
|
+
expect(messages.systemParam).toBe('system prompt');
|
|
35
|
+
});
|
|
36
|
+
it('should define correct structure for CompactionResult', () => {
|
|
37
|
+
const result = {
|
|
38
|
+
messages: [],
|
|
39
|
+
compacted: true,
|
|
40
|
+
method: 'llm',
|
|
41
|
+
};
|
|
42
|
+
expect(result.compacted).toBe(true);
|
|
43
|
+
expect(result.method).toBe('llm');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('loop-utils', () => {
|
|
47
|
+
it('buildToolLogEntry should format tool calls correctly', () => {
|
|
48
|
+
const log = buildToolLogEntry('Read', 'file.txt', 'file contents here');
|
|
49
|
+
expect(log).toContain('Read');
|
|
50
|
+
expect(log).toContain('file.txt');
|
|
51
|
+
expect(log).toContain('file contents here');
|
|
52
|
+
});
|
|
53
|
+
it('buildToolLogEntry should truncate long inputs and outputs', () => {
|
|
54
|
+
const longInput = 'a'.repeat(200);
|
|
55
|
+
const longOutput = 'b'.repeat(300);
|
|
56
|
+
const log = buildToolLogEntry('Write', longInput, longOutput);
|
|
57
|
+
expect(log.length).toBeLessThan(320);
|
|
58
|
+
expect(log).toContain('...');
|
|
59
|
+
});
|
|
60
|
+
it('logIteration should not throw', () => {
|
|
61
|
+
expect(() => logIteration('test', 0, 10, 'model-id')).not.toThrow();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the Anthropic provider adapter.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
5
|
+
import { AnthropicAdapter } from '../providers/adapters/anthropic-adapter.js';
|
|
6
|
+
// Mock the Anthropic client - create a single shared mock
|
|
7
|
+
const mockMessagesCreate = vi.fn();
|
|
8
|
+
const mockMessagesStream = vi.fn();
|
|
9
|
+
vi.mock('../providers/anthropic.js', () => ({
|
|
10
|
+
getAnthropicClient: vi.fn(() => ({
|
|
11
|
+
messages: {
|
|
12
|
+
create: mockMessagesCreate,
|
|
13
|
+
stream: mockMessagesStream,
|
|
14
|
+
},
|
|
15
|
+
})),
|
|
16
|
+
}));
|
|
17
|
+
// Mock utils
|
|
18
|
+
vi.mock('../providers/utils.js', () => ({
|
|
19
|
+
buildSystemParam: vi.fn((content, cache) => ({ content, cache })),
|
|
20
|
+
addToolCacheBreakpoint: vi.fn((defs) => defs),
|
|
21
|
+
contentToText: vi.fn((content) => String(content)),
|
|
22
|
+
stripProvider: vi.fn((model) => model.replace(/^[^/]+\//, '')),
|
|
23
|
+
buildThinkingConfig: vi.fn(() => null),
|
|
24
|
+
}));
|
|
25
|
+
// Mock context manager
|
|
26
|
+
vi.mock('../providers/context-manager.js', () => ({
|
|
27
|
+
compactMessages: vi.fn(async (messages) => ({
|
|
28
|
+
messages,
|
|
29
|
+
compacted: false,
|
|
30
|
+
})),
|
|
31
|
+
anthropicFormatHelper: {
|
|
32
|
+
isToolResult: () => false,
|
|
33
|
+
truncateToolResult: (item) => item,
|
|
34
|
+
serialize: () => '',
|
|
35
|
+
buildSummaryMessage: (s) => ({ role: 'user', content: [{ type: 'text', text: s }] }),
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
// Mock observability
|
|
39
|
+
vi.mock('../providers/observability.js', () => ({
|
|
40
|
+
toCostDetails: vi.fn(() => ({ input: 0.001, output: 0.002, total: 0.003 })),
|
|
41
|
+
}));
|
|
42
|
+
// Mock usage tracking
|
|
43
|
+
vi.mock('../usage.js', () => ({
|
|
44
|
+
buildUsageRecord: vi.fn((opts) => opts),
|
|
45
|
+
recordUsage: vi.fn(),
|
|
46
|
+
}));
|
|
47
|
+
describe('AnthropicAdapter', () => {
|
|
48
|
+
let adapter;
|
|
49
|
+
let config;
|
|
50
|
+
let options;
|
|
51
|
+
beforeEach(async () => {
|
|
52
|
+
adapter = new AnthropicAdapter();
|
|
53
|
+
config = {
|
|
54
|
+
gateway: { port: 18790, mode: 'local' },
|
|
55
|
+
agents: { default: 'main', list: {} },
|
|
56
|
+
models: { providers: {}, aliases: {}, promptCaching: true },
|
|
57
|
+
channels: {
|
|
58
|
+
telegram: {
|
|
59
|
+
enabled: false,
|
|
60
|
+
token: 'test-token',
|
|
61
|
+
allowFrom: [],
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
cron: { jobs: [] },
|
|
65
|
+
heartbeat: { intervalMs: 300000, prompt: 'HEARTBEAT' },
|
|
66
|
+
};
|
|
67
|
+
options = {
|
|
68
|
+
model: 'anthropic/claude-opus-4',
|
|
69
|
+
maxTokens: 1000,
|
|
70
|
+
};
|
|
71
|
+
// Clear mock call history
|
|
72
|
+
mockMessagesCreate.mockClear();
|
|
73
|
+
mockMessagesStream.mockClear();
|
|
74
|
+
const { recordUsage } = await import('../usage.js');
|
|
75
|
+
vi.mocked(recordUsage).mockClear();
|
|
76
|
+
});
|
|
77
|
+
describe('buildMessages', () => {
|
|
78
|
+
it('should extract system message and build API messages', () => {
|
|
79
|
+
const messages = [
|
|
80
|
+
{ role: 'system', content: 'You are helpful' },
|
|
81
|
+
{ role: 'user', content: 'Hello' },
|
|
82
|
+
{ role: 'assistant', content: 'Hi!' },
|
|
83
|
+
];
|
|
84
|
+
const result = adapter.buildMessages(messages, options, config);
|
|
85
|
+
expect(result.messages).toHaveLength(2); // system excluded
|
|
86
|
+
expect(result.messages[0].role).toBe('user');
|
|
87
|
+
expect(result.messages[1].role).toBe('assistant');
|
|
88
|
+
expect(result.systemParam).toBeDefined();
|
|
89
|
+
});
|
|
90
|
+
it('should handle missing system message', () => {
|
|
91
|
+
const messages = [
|
|
92
|
+
{ role: 'user', content: 'Hello' },
|
|
93
|
+
];
|
|
94
|
+
const result = adapter.buildMessages(messages, options, config);
|
|
95
|
+
expect(result.messages).toHaveLength(1);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('buildToolDefs', () => {
|
|
99
|
+
it('should return tool definitions with cache breakpoints when caching enabled', async () => {
|
|
100
|
+
const { addToolCacheBreakpoint } = await import('../providers/utils.js');
|
|
101
|
+
vi.mocked(addToolCacheBreakpoint).mockClear();
|
|
102
|
+
const toolDefs = [
|
|
103
|
+
{ name: 'Read', description: 'Read file' },
|
|
104
|
+
{ name: 'Write', description: 'Write file' },
|
|
105
|
+
];
|
|
106
|
+
const result = adapter.buildToolDefs(toolDefs, config);
|
|
107
|
+
expect(result).toHaveLength(2);
|
|
108
|
+
expect(result[0].name).toBe('Read');
|
|
109
|
+
expect(addToolCacheBreakpoint).toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
it('should skip cache breakpoints when promptCaching is false', async () => {
|
|
112
|
+
const { addToolCacheBreakpoint } = await import('../providers/utils.js');
|
|
113
|
+
vi.mocked(addToolCacheBreakpoint).mockClear();
|
|
114
|
+
const noCacheConfig = {
|
|
115
|
+
...config,
|
|
116
|
+
models: { ...config.models, promptCaching: false },
|
|
117
|
+
};
|
|
118
|
+
const toolDefs = [{ name: 'Read', description: 'Read file' }];
|
|
119
|
+
adapter.buildToolDefs(toolDefs, noCacheConfig);
|
|
120
|
+
expect(addToolCacheBreakpoint).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe('call', () => {
|
|
124
|
+
it('should normalize Anthropic response without tool calls', async () => {
|
|
125
|
+
mockMessagesCreate.mockResolvedValue({
|
|
126
|
+
content: [{ type: 'text', text: 'Hello!' }],
|
|
127
|
+
stop_reason: 'end_turn',
|
|
128
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
129
|
+
});
|
|
130
|
+
const providerMessages = {
|
|
131
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
132
|
+
systemParam: { content: 'system' },
|
|
133
|
+
};
|
|
134
|
+
const result = await adapter.call(providerMessages, [], options, config);
|
|
135
|
+
expect(result.hasToolCalls).toBe(false);
|
|
136
|
+
expect(result.textContent).toBe('Hello!');
|
|
137
|
+
expect(result.usage?.inputTokens).toBe(100);
|
|
138
|
+
expect(result.usage?.outputTokens).toBe(50);
|
|
139
|
+
});
|
|
140
|
+
it('should normalize Anthropic response with tool calls', async () => {
|
|
141
|
+
mockMessagesCreate.mockResolvedValue({
|
|
142
|
+
content: [
|
|
143
|
+
{ type: 'text', text: 'Let me read that file' },
|
|
144
|
+
{
|
|
145
|
+
type: 'tool_use',
|
|
146
|
+
id: 'tool_123',
|
|
147
|
+
name: 'Read',
|
|
148
|
+
input: { file_path: 'test.txt' },
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
stop_reason: 'tool_use',
|
|
152
|
+
usage: { input_tokens: 150, output_tokens: 80 },
|
|
153
|
+
});
|
|
154
|
+
const providerMessages = {
|
|
155
|
+
messages: [{ role: 'user', content: 'Read test.txt' }],
|
|
156
|
+
systemParam: { content: 'system' },
|
|
157
|
+
};
|
|
158
|
+
const result = await adapter.call(providerMessages, [], options, config);
|
|
159
|
+
expect(result.hasToolCalls).toBe(true);
|
|
160
|
+
expect(result.toolCalls).toHaveLength(1);
|
|
161
|
+
expect(result.toolCalls[0].id).toBe('tool_123');
|
|
162
|
+
expect(result.toolCalls[0].name).toBe('Read');
|
|
163
|
+
expect(result.toolCalls[0].args).toEqual({ file_path: 'test.txt' });
|
|
164
|
+
expect(result.textContent).toBe('Let me read that file');
|
|
165
|
+
});
|
|
166
|
+
it('should handle cache metrics logging', async () => {
|
|
167
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
168
|
+
mockMessagesCreate.mockResolvedValue({
|
|
169
|
+
content: [{ type: 'text', text: 'Cached response' }],
|
|
170
|
+
stop_reason: 'end_turn',
|
|
171
|
+
usage: {
|
|
172
|
+
input_tokens: 100,
|
|
173
|
+
output_tokens: 50,
|
|
174
|
+
cache_read_input_tokens: 500,
|
|
175
|
+
cache_creation_input_tokens: 100,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
const providerMessages = {
|
|
179
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
180
|
+
};
|
|
181
|
+
await adapter.call(providerMessages, [], options, config);
|
|
182
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('[cache]'));
|
|
183
|
+
});
|
|
184
|
+
it('should use streaming for large xhigh thinking requests', async () => {
|
|
185
|
+
const { buildThinkingConfig } = await import('../providers/utils.js');
|
|
186
|
+
vi.mocked(buildThinkingConfig).mockReturnValueOnce({ budget: 32768, maxTokens: 36864 });
|
|
187
|
+
const finalMessage = vi.fn().mockResolvedValue({
|
|
188
|
+
content: [{ type: 'text', text: 'Streamed response' }],
|
|
189
|
+
stop_reason: 'end_turn',
|
|
190
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
191
|
+
});
|
|
192
|
+
mockMessagesStream.mockReturnValue({ finalMessage });
|
|
193
|
+
const providerMessages = {
|
|
194
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
195
|
+
};
|
|
196
|
+
const result = await adapter.call(providerMessages, [], { ...options, thinking: 'xhigh' }, config);
|
|
197
|
+
expect(mockMessagesCreate).not.toHaveBeenCalled();
|
|
198
|
+
expect(mockMessagesStream).toHaveBeenCalledWith(expect.objectContaining({
|
|
199
|
+
max_tokens: 36864,
|
|
200
|
+
thinking: { type: 'enabled', budget_tokens: 32768 },
|
|
201
|
+
}));
|
|
202
|
+
expect(result.textContent).toBe('Streamed response');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
describe('appendAssistantResponse', () => {
|
|
206
|
+
it('should append raw response to messages', () => {
|
|
207
|
+
const providerMessages = {
|
|
208
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
209
|
+
};
|
|
210
|
+
const rawResponse = {
|
|
211
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
212
|
+
};
|
|
213
|
+
adapter.appendAssistantResponse(providerMessages, rawResponse);
|
|
214
|
+
expect(providerMessages.messages).toHaveLength(2);
|
|
215
|
+
expect(providerMessages.messages[1].role).toBe('assistant');
|
|
216
|
+
expect(providerMessages.messages[1].content).toBe(rawResponse.content);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('appendToolResult', () => {
|
|
220
|
+
it('should append tool result as user message', () => {
|
|
221
|
+
const providerMessages = {
|
|
222
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
223
|
+
};
|
|
224
|
+
adapter.appendToolResult(providerMessages, 'tool_123', 'file contents', false);
|
|
225
|
+
expect(providerMessages.messages).toHaveLength(2);
|
|
226
|
+
expect(providerMessages.messages[1].role).toBe('user');
|
|
227
|
+
expect(providerMessages.messages[1].content).toHaveLength(1);
|
|
228
|
+
expect(providerMessages.messages[1].content[0].type).toBe('tool_result');
|
|
229
|
+
expect(providerMessages.messages[1].content[0].tool_use_id).toBe('tool_123');
|
|
230
|
+
});
|
|
231
|
+
it('should mark error results', () => {
|
|
232
|
+
const providerMessages = {
|
|
233
|
+
messages: [],
|
|
234
|
+
};
|
|
235
|
+
adapter.appendToolResult(providerMessages, 'tool_123', 'error!', true);
|
|
236
|
+
expect(providerMessages.messages[0].content[0].is_error).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe('compactMessages', () => {
|
|
240
|
+
it('should delegate to generic compactMessages with anthropicFormatHelper', async () => {
|
|
241
|
+
const { compactMessages } = await import('../providers/context-manager.js');
|
|
242
|
+
const providerMessages = {
|
|
243
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
244
|
+
};
|
|
245
|
+
const result = await adapter.compactMessages(providerMessages, {}, 1, config);
|
|
246
|
+
expect(compactMessages).toHaveBeenCalled();
|
|
247
|
+
expect(result.compacted).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe('recordUsage', () => {
|
|
251
|
+
it('should record usage with costs', async () => {
|
|
252
|
+
const { recordUsage } = await import('../usage.js');
|
|
253
|
+
adapter.recordUsage('claude-opus-4', { inputTokens: 100, outputTokens: 50 }, 'api', 'agent-1');
|
|
254
|
+
expect(recordUsage).toHaveBeenCalled();
|
|
255
|
+
});
|
|
256
|
+
it('should skip recording when tokens are zero', async () => {
|
|
257
|
+
const { recordUsage } = await import('../usage.js');
|
|
258
|
+
vi.mocked(recordUsage).mockClear();
|
|
259
|
+
adapter.recordUsage('claude-opus-4', { inputTokens: 0, outputTokens: 0 }, 'api');
|
|
260
|
+
// recordUsage should not be called when tokens are zero
|
|
261
|
+
expect(recordUsage).not.toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -889,7 +889,6 @@ describe('Health endpoint', () => {
|
|
|
889
889
|
expect(body).toHaveProperty('features');
|
|
890
890
|
expect(body.features).toHaveProperty('telegram');
|
|
891
891
|
expect(body.features).toHaveProperty('discord');
|
|
892
|
-
expect(body.features).toHaveProperty('browser');
|
|
893
892
|
expect(body.features).toHaveProperty('voice');
|
|
894
893
|
});
|
|
895
894
|
it('GET /api/dashboard/health returns env var status', async () => {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { spawnSync } from 'child_process';
|
|
3
|
-
import { join } from 'path';
|
|
4
3
|
describe('cli integration', () => {
|
|
5
|
-
it('onboard --dry-run exits successfully without prompting', () => {
|
|
6
|
-
const
|
|
7
|
-
const result = spawnSync(process.execPath, [tsxCli, 'src/cli.ts', 'onboard', '--dry-run'], {
|
|
4
|
+
it('onboard --dry-run exits successfully without prompting', { timeout: 30000 }, () => {
|
|
5
|
+
const result = spawnSync(process.execPath, ['--import', 'tsx', 'src/cli.ts', 'onboard', '--dry-run'], {
|
|
8
6
|
cwd: process.cwd(),
|
|
9
7
|
encoding: 'utf-8',
|
|
10
8
|
});
|