diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b37ea389..d6eb0532f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,3 +40,13 @@ Please include in your PR: - [ ] Confirm you understand what the code does AI PRs are first-class citizens here. We just want transparency so reviewers know what to look for. + +## Current Focus & Roadmap 🗺 + +We are currently prioritizing: +- **Stability**: Fixing edge cases in channel connections (WhatsApp/Telegram). +- **UX**: Improving the onboarding wizard and error messages. +- **Skills**: Expanding the library of bundled skills and improving the Skill Creation developer experience. +- **Performance**: Optimizing token usage and compaction logic. + +Check the [GitHub Issues](https://github.com/clawdbot/clawdbot/issues) for "good first issue" labels! diff --git a/docs/tools/creating-skills.md b/docs/tools/creating-skills.md new file mode 100644 index 000000000..77e3415d2 --- /dev/null +++ b/docs/tools/creating-skills.md @@ -0,0 +1,41 @@ +# Creating Custom Skills 🛠 + +Clawdbot is designed to be easily extensible. "Skills" are the primary way to add new capabilities to your assistant. + +## What is a Skill? +A skill is a directory containing a `SKILL.md` file (which provides instructions and tool definitions to the LLM) and optionally some scripts or resources. + +## Step-by-Step: Your First Skill + +### 1. Create the Directory +Skills live in your workspace, usually `~/clawd/skills/`. Create a new folder for your skill: +```bash +mkdir -p ~/clawd/skills/hello-world +``` + +### 2. Define the `SKILL.md` +Create a `SKILL.md` file in that directory. This file uses YAML frontmatter for metadata and Markdown for instructions. + +```markdown +--- +name: hello_world +description: A simple skill that says hello. +--- + +# Hello World Skill +When the user asks for a greeting, use the `echo` tool to say "Hello from your custom skill!". +``` + +### 3. Add Tools (Optional) +You can define custom tools in the frontmatter or instruct the agent to use existing system tools (like `bash` or `browser`). + +### 4. Refresh Clawdbot +Ask your agent to "refresh skills" or restart the gateway. Clawdbot will discover the new directory and index the `SKILL.md`. + +## Best Practices +- **Be Concise**: Instruct the model on *what* to do, not how to be an AI. +- **Safety First**: If your skill uses `bash`, ensure the prompts don't allow arbitrary command injection from untrusted user input. +- **Test Locally**: Use `clawdbot agent --message "use my new skill"` to test. + +## Shared Skills +You can also browse and contribute skills to [ClawdHub](https://clawdhub.com). diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index a330423b3..5a672c581 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -137,7 +137,12 @@ export function resolveConfiguredModelRef(params: { aliasIndex, }); if (resolved) return resolved.ref; - // TODO(steipete): drop this fallback once provider-less agents.defaults.model is fully deprecated. + + // Default to anthropic if no provider is specified, but warn as this is deprecated. + console.warn( + `[clawdbot] Model "${trimmed}" specified without provider. Falling back to "anthropic/${trimmed}". ` + + `Please use "anthropic/${trimmed}" in your config.`, + ); return { provider: "anthropic", model: trimmed }; } return { provider: params.defaultProvider, model: params.defaultModel }; @@ -153,20 +158,20 @@ export function resolveDefaultModelForAgent(params: { const cfg = agentModelOverride && agentModelOverride.length > 0 ? { - ...params.cfg, - agents: { - ...params.cfg.agents, - defaults: { - ...params.cfg.agents?.defaults, - model: { - ...(typeof params.cfg.agents?.defaults?.model === "object" - ? params.cfg.agents.defaults.model - : undefined), - primary: agentModelOverride, - }, + ...params.cfg, + agents: { + ...params.cfg.agents, + defaults: { + ...params.cfg.agents?.defaults, + model: { + ...(typeof params.cfg.agents?.defaults?.model === "object" + ? params.cfg.agents.defaults.model + : undefined), + primary: agentModelOverride, }, }, - } + }, + } : params.cfg; return resolveConfiguredModelRef({ cfg, @@ -282,8 +287,8 @@ export function resolveAllowedModelRef(params: { }): | { ref: ModelRef; key: string } | { - error: string; - } { + error: string; + } { const trimmed = params.raw.trim(); if (!trimmed) return { error: "invalid model: empty" }; diff --git a/src/auto-reply/heartbeat.ts b/src/auto-reply/heartbeat.ts index 50567ad87..cca6c6ad1 100644 --- a/src/auto-reply/heartbeat.ts +++ b/src/auto-reply/heartbeat.ts @@ -32,6 +32,8 @@ export function isHeartbeatContentEffectivelyEmpty(content: string | undefined | // This intentionally does NOT skip lines like "#TODO" or "#hashtag" which might be content // (Those aren't valid markdown headers - ATX headers require space after #) if (/^#+(\s|$)/.test(trimmed)) continue; + // Skip empty markdown list items like "- [ ]" or "* [ ]" or just "- " + if (/^[-*+]\s*(\[[\sXx]?\]\s*)?$/.test(trimmed)) continue; // Found a non-empty, non-comment line - there's actionable content return false; }