diff --git a/.claude/skills/add-slack/SKILL.md b/.claude/skills/add-slack/SKILL.md index fa940b2db..e57e600fc 100644 --- a/.claude/skills/add-slack/SKILL.md +++ b/.claude/skills/add-slack/SKILL.md @@ -18,6 +18,7 @@ Skip to **Credentials** if all of these are already in place: - `src/channels/slack.ts` exists - `src/channels/slack-registration.test.ts` exists - `src/channels/index.ts` contains `import './slack.js';` +- `container/skills/slack-formatting/SKILL.md` exists - `@chat-adapter/slack` is listed in `package.json` dependencies Otherwise continue. Every step below is safe to re-run. @@ -33,8 +34,15 @@ git fetch origin channels ```bash git show origin/channels:src/channels/slack.ts > src/channels/slack.ts git show origin/channels:src/channels/slack-registration.test.ts > src/channels/slack-registration.test.ts +mkdir -p container/skills/slack-formatting +git show origin/channels:container/skills/slack-formatting/SKILL.md > container/skills/slack-formatting/SKILL.md ``` +The `slack-formatting` container skill is part of the channel payload: it +reaches agents via `~/.claude/skills` (synced at spawn) and teaches Slack's +mrkdwn syntax. Trunk does not ship it — without this copy step agents send +Slack messages with generic markdown that renders literally. + ### 3. Append the self-registration import Append to `src/channels/index.ts` (skip if the line is already present): diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f246fc00..0ffe0eeff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to NanoClaw will be documented in this file. ## [Unreleased] -- [BREAKING] **`whatsapp-formatting` container skill moved out of trunk — WhatsApp installs must re-run `/add-whatsapp` after updating.** The skill (`container/skills/whatsapp-formatting/`) now lives on the `channels` branch as part of the WhatsApp payload and is copied in by `/add-whatsapp` and the setup channel installers; trunk no longer ships it, so non-WhatsApp installs stop carrying WhatsApp formatting instructions in every agent's composed CLAUDE.md. **Existing WhatsApp installs:** updating removes the skill from your working tree, and the next container spawn regenerates each group's CLAUDE.md without the `skill-whatsapp-formatting.md` fragment — agents will silently lose WhatsApp formatting rules (asterisk bold, no headers, etc.) until you re-run `/add-whatsapp` (idempotent; it now restores the skill) or copy it back directly: `mkdir -p container/skills/whatsapp-formatting && git show origin/channels:container/skills/whatsapp-formatting/SKILL.md > container/skills/whatsapp-formatting/SKILL.md && git show origin/channels:container/skills/whatsapp-formatting/instructions.md > container/skills/whatsapp-formatting/instructions.md`. +- [BREAKING] **Channel formatting skills (`whatsapp-formatting`, `slack-formatting`) moved out of trunk — WhatsApp and Slack installs must re-run their `/add-` skill after updating.** Both skills now live on the `channels` branch as part of their channel's payload and are copied in by `/add-whatsapp` / `/add-slack` and the setup channel installers; trunk no longer ships them, so installs without those channels stop carrying channel-specific formatting instructions in every agent's context. **Existing installs:** updating removes the skills from your working tree. For WhatsApp, the next container spawn also regenerates each group's composed CLAUDE.md without the `skill-whatsapp-formatting.md` fragment — agents silently lose WhatsApp formatting rules (asterisk bold, no headers, etc.). For Slack, the `slack-formatting` skill drops out of `~/.claude/skills`, so agents lose the mrkdwn guidance. Re-run `/add-whatsapp` and/or `/add-slack` (idempotent; they now restore the skills), or copy back directly, e.g. `mkdir -p container/skills/whatsapp-formatting && git show origin/channels:container/skills/whatsapp-formatting/instructions.md > container/skills/whatsapp-formatting/instructions.md` (plus the sibling `SKILL.md`, and `container/skills/slack-formatting/SKILL.md` for Slack). - **Pre-task script failures back their series off instead of spinning.** A `--script` that errors lands the occurrence as a failed run (`script-skip:error` ack → `failed` status); recurrence reads the series' trailing failed streak and re-arms at `max(cron next, now + 2·2^(n−1) min, cap 60)`; after 8 consecutive failures the series is auto-paused with a host-written note in its run log (`ncl tasks resume` revives it). A deliberate `wakeAgent:false` gate is a normal run and never backs off. Also fixed: an explicitly-addressed `` in a task fire's final text now delivers as a deliberate send (previously suppressed as a turn-reply echo → zero delivery when the agent skipped the MCP tool); identical echoes of an MCP send are dropped in the runner, where the duplication originates. - [BREAKING] **Scheduled tasks moved from MCP tools to `ncl tasks`.** The six scheduling MCP tools are no longer exposed to agent containers; agents and operators manage tasks with `ncl tasks list/get/create/update/cancel/pause/resume/delete`. New tasks run from a per-agent-group system session rather than waking the chat session that created them, and task writes are not approval-gated inside the owning group. **Migration:** [docs/ncl-tasks-migration.md](docs/ncl-tasks-migration.md). - **Optional per-container resource caps.** `CONTAINER_CPU_LIMIT` and `CONTAINER_MEMORY_LIMIT` pass through to `docker run` as `--cpus` / `--memory` (`container-runner.ts`). Both empty by default — no flag added, spawn args byte-identical to today — so existing installs are unaffected. Set them to cap an agent container's CPU/memory so one agent can't monopolize the host (e.g. `CONTAINER_CPU_LIMIT=2`, `CONTAINER_MEMORY_LIMIT=8g`). Swap is intentionally not managed here: `--memory` is a hard cap on a swapless host. diff --git a/CLAUDE.md b/CLAUDE.md index dd21e7f07..8f4b449a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,7 +79,7 @@ For ad-hoc queries from skills or scripts, use the in-tree wrapper rather than t | `src/channels/` | Channel adapter infra (registry, Chat SDK bridge); specific channel adapters are skill-installed from the `channels` branch | | `src/providers/` | Host-side provider container-config (`claude` baked in; `opencode` etc. installed from the `providers` branch) | | `container/agent-runner/src/` | Agent-runner: poll loop, formatter, provider abstraction, MCP tools, destinations | -| `container/skills/` | Container skills mounted into every agent session (`agent-browser`, `frontend-engineer`, `onecli-gateway`, `self-customize`, `slack-formatting`, `vercel-cli`, `welcome`; channel-specific skills like `whatsapp-formatting` install with their channel) | +| `container/skills/` | Container skills mounted into every agent session (`agent-browser`, `frontend-engineer`, `onecli-gateway`, `self-customize`, `vercel-cli`, `welcome`; channel-specific skills like `slack-formatting` and `whatsapp-formatting` install with their channel) | | `groups//` | Per-agent-group filesystem (CLAUDE.md, skills) — agent-runner source is a shared read-only mount, not copied per group | | `scripts/init-first-agent.ts` | Bootstrap the first DM-wired agent (used by `/init-first-agent` skill) | | `migrate-v2.sh` + `setup/migrate-v2/` | v1→v2 migration. Standalone script: `bash migrate-v2.sh`. Seeds DB, copies groups/sessions, installs channels, builds container, offers service switchover, then hands off to `/migrate-from-v1` skill for owner setup and CLAUDE.md cleanup. See [docs/migration-dev.md](docs/migration-dev.md). | @@ -183,7 +183,7 @@ Four types of skills. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full taxono - **Channel/provider install skills** — copy the relevant module(s) in from the `channels` or `providers` branch, wire imports, install pinned deps (e.g. `/add-discord`, `/add-slack`, `/add-whatsapp`, `/add-opencode`). - **Utility skills** — ship code files alongside `SKILL.md` (e.g. a `scripts/` CLI or helper). - **Operational skills** — instruction-only workflows (`/setup`, `/debug`, `/customize`, `/init-first-agent`, `/manage-channels`, `/init-onecli`, `/update-nanoclaw`). -- **Container skills** — loaded inside agent containers at runtime (`container/skills/`: `agent-browser`, `frontend-engineer`, `onecli-gateway`, `self-customize`, `slack-formatting`, `vercel-cli`, `welcome`; channel-specific skills like `whatsapp-formatting` are copied in by their `/add-` skill). +- **Container skills** — loaded inside agent containers at runtime (`container/skills/`: `agent-browser`, `frontend-engineer`, `onecli-gateway`, `self-customize`, `vercel-cli`, `welcome`; channel-specific skills like `slack-formatting` and `whatsapp-formatting` are copied in by their `/add-` skill). | Skill | When to Use | |-------|-------------| diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85fff8de7..aea8972d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,7 +92,7 @@ Skills that run inside the agent container, not on the host. These teach the Nan **Location:** `container/skills//` -**Examples:** `agent-browser` (web browsing), `frontend-engineer`, `onecli-gateway` (OneCLI proxy usage), `self-customize`, `slack-formatting` (Slack mrkdwn syntax), `vercel-cli`, `welcome`, `whatsapp-formatting` (channels branch; installed by `/add-whatsapp`) +**Examples:** `agent-browser` (web browsing), `frontend-engineer`, `onecli-gateway` (OneCLI proxy usage), `self-customize`, `vercel-cli`, `welcome`; channel-specific: `slack-formatting` (Slack mrkdwn syntax) and `whatsapp-formatting` (channels branch; installed by `/add-slack` / `/add-whatsapp`) **Key difference:** You never invoke these from a coding-agent session on the host, the way you run `/setup` or `/update-nanoclaw` in Claude Code/Codex/OpenCode. They're mounted into the sandbox and loaded by the NanoClaw agent itself, shaping how it behaves when you chat with it. diff --git a/container/skills/slack-formatting/SKILL.md b/container/skills/slack-formatting/SKILL.md deleted file mode 100644 index 29a1b8716..000000000 --- a/container/skills/slack-formatting/SKILL.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -name: slack-formatting -description: Format messages for Slack using mrkdwn syntax. Use when responding to Slack channels (folder starts with "slack_" or JID contains slack identifiers). ---- - -# Slack Message Formatting (mrkdwn) - -When responding to Slack channels, use Slack's mrkdwn syntax instead of standard Markdown. - -## How to detect Slack context - -Check your group folder name or workspace path: -- Folder starts with `slack_` (e.g., `slack_engineering`, `slack_general`) -- Or check `/workspace/group/` path for `slack_` prefix - -## Formatting reference - -### Text styles - -| Style | Syntax | Example | -|-------|--------|---------| -| Bold | `*text*` | *bold text* | -| Italic | `_text_` | _italic text_ | -| Strikethrough | `~text~` | ~strikethrough~ | -| Code (inline) | `` `code` `` | `inline code` | -| Code block | ` ```code``` ` | Multi-line code | - -### Links and mentions - -``` - # Named link - # Auto-linked URL -<@U1234567890> # Mention user by ID -<#C1234567890> # Mention channel by ID - # @here - # @channel -``` - -### Lists - -Slack supports simple bullet lists but NOT numbered lists: - -``` -• First item -• Second item -• Third item -``` - -Use `•` (bullet character) or `- ` or `* ` for bullets. - -### Block quotes - -``` -> This is a block quote -> It can span multiple lines -``` - -### Emoji - -Use standard emoji shortcodes: `:white_check_mark:`, `:x:`, `:rocket:`, `:tada:` - -## What NOT to use - -- **NO** `##` headings (use `*Bold text*` for headers instead) -- **NO** `**double asterisks**` for bold (use `*single asterisks*`) -- **NO** `[text](url)` links (use `` instead) -- **NO** `1.` numbered lists (use bullets with numbers: `• 1. First`) -- **NO** tables (use code blocks or plain text alignment) -- **NO** `---` horizontal rules - -## Example message - -``` -*Daily Standup Summary* - -_March 21, 2026_ - -• *Completed:* Fixed authentication bug in login flow -• *In Progress:* Building new dashboard widgets -• *Blocked:* Waiting on API access from DevOps - -> Next sync: Monday 10am - -:white_check_mark: All tests passing | -``` - -## Quick rules - -1. Use `*bold*` not `**bold**` -2. Use `` not `[text](url)` -3. Use `•` bullets, avoid numbered lists -4. Use `:emoji:` shortcodes -5. Quote blocks with `>` -6. Skip headings — use bold text instead diff --git a/setup/add-slack.sh b/setup/add-slack.sh index 3aa63d757..4d2db7d31 100755 --- a/setup/add-slack.sh +++ b/setup/add-slack.sh @@ -51,6 +51,7 @@ fi need_install() { [ ! -f src/channels/slack.ts ] && return 0 + [ ! -f container/skills/slack-formatting/SKILL.md ] && return 0 ! grep -q "^import './slack.js';" src/channels/index.ts 2>/dev/null && return 0 return 1 } @@ -67,6 +68,10 @@ if need_install; then log "Copying adapter from ${CHANNELS_BRANCH}…" git show "${CHANNELS_BRANCH}:src/channels/slack.ts" > src/channels/slack.ts + # Slack formatting container skill — reaches agents via ~/.claude/skills. + mkdir -p container/skills/slack-formatting + git show "${CHANNELS_BRANCH}:container/skills/slack-formatting/SKILL.md" > container/skills/slack-formatting/SKILL.md + # Append self-registration import if missing. if ! grep -q "^import './slack.js';" src/channels/index.ts; then echo "import './slack.js';" >> src/channels/index.ts diff --git a/setup/install-slack.sh b/setup/install-slack.sh index 5e9318258..bee97f80c 100755 --- a/setup/install-slack.sh +++ b/setup/install-slack.sh @@ -15,6 +15,7 @@ echo "=== NANOCLAW SETUP: INSTALL_SLACK ===" needs_install=false [[ -f src/channels/slack.ts ]] || needs_install=true +[[ -f container/skills/slack-formatting/SKILL.md ]] || needs_install=true grep -q "import './slack.js';" src/channels/index.ts || needs_install=true grep -q '"@chat-adapter/slack"' package.json || needs_install=true [[ -d node_modules/@chat-adapter/slack ]] || needs_install=true @@ -30,6 +31,8 @@ git fetch origin channels echo "STEP: copy-files" git show origin/channels:src/channels/slack.ts > src/channels/slack.ts +mkdir -p container/skills/slack-formatting +git show origin/channels:container/skills/slack-formatting/SKILL.md > container/skills/slack-formatting/SKILL.md echo "STEP: register-import" if ! grep -q "import './slack.js';" src/channels/index.ts; then