- Move runCtx/runCancel creation before event handler registration and
QR loop so Stop() can cancel at any point during startup
- Replace blocking QR event loop in Start() with a background goroutine
that selects on runCtx.Done(), preventing Start() from hanging
indefinitely when waiting for QR scan
- Track all background goroutines (QR handler, reconnect) with
sync.WaitGroup; Stop() waits for them to finish before releasing
client/container resources
- Cancel runCtx on error paths in Start() to avoid leaked contexts
Fixes resource leak introduced in #655.
* chore(docker): move Dockerfile into docker/ directory
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(docker): add entrypoint script to goreleaser Dockerfile
- entrypoint.sh: on first run (config and workspace both absent) runs
picoclaw onboard then exits for the user to configure; subsequent
starts exec picoclaw gateway directly
- Dockerfile.goreleaser: copy and use entrypoint.sh, run as root
- .goreleaser.yaml: update dockerfile path, add entrypoint.sh to
extra_files so it is included in the docker build context
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(docker): update docker-compose to use pre-built image and bind mount
- Use docker.io/sipeed/picoclaw:latest instead of building locally
- Replace named volume with bind mount ./data:/root/.picoclaw
- Move docker-compose.yml into docker/ directory
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update Docker Compose section to reflect new docker/ layout
- Use docker compose -f docker/docker-compose.yml for all commands
- Update setup flow: first run generates docker/data/config.json,
container exits, user edits config, then restarts
- Replace "Rebuild" section with "Update" (docker pull) since the
compose file now uses the pre-built sipeed/picoclaw image
- Apply same changes to README.zh.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(docker): use restart: on-failure to prevent restart after first-run setup
unless-stopped restarts the container regardless of exit code, causing
an infinite loop when entrypoint exits 0 after the initial onboard.
on-failure only restarts on non-zero exit (i.e. crashes), so the
container stays stopped after setup until the user restarts it manually.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: sync Docker Compose section across all language READMEs
Apply the same updates as the English/Chinese READMEs:
- Use docker compose -f docker/docker-compose.yml for all commands
- Update setup flow to first-run auto-config pattern
- Replace build/rebuild section with update via docker pull
- Affected: README.fr.md, README.ja.md, README.pt-br.md, README.vi.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Define PlaceholderCapable, TypingCapable, and ReactionCapable interfaces
and have BaseChannel.HandleMessage auto-detect and trigger all three as
independent pipelines on inbound messages. This replaces the scattered
manual orchestration code in each channel's handleMessage with a single
unified dispatch in the framework layer.
Changes:
- Add PlaceholderCapable interface to interfaces.go
- Add ReactionCapable + RecordReactionUndo to interfaces.go
- BaseChannel.HandleMessage auto-triggers Typing → Reaction → Placeholder
- Manager gains reactionUndos sync.Map with TTL janitor cleanup
- Telegram: extract SendPlaceholder from manual code, add StartTyping
- Discord: add SendPlaceholder + StartTyping
- Pico: add SendPlaceholder (uses Pico Protocol message.create)
- Slack: extract ReactToMessage from manual code
- OneBot: extract ReactToMessage, remove leaked pendingEmojiMsg sync.Map
- LINE: move group-chat guard into StartTyping, remove manual orchestration
- Config: add Placeholder to PicoConfig; remove from Slack/LINE/OneBot
(no MessageEditor, so placeholder config was dead code)
Port changes that were applied to the old pkg/channels/*.go files on main
to their new locations in channel subpackages:
- telegram: precompile regex, var transcribedText, GetModelName()
- discord: var transcribedText declaration
- onebot: resp.Body.Close(), "canceled" spelling, remove empty line
- slack: named return values in parseSlackChatID
- wecom: remove sendMarkdownMessage dead code
- whatsapp: resp.Body.Close() after Dial
- gateway/helpers: remove unused errors import
Prevents potential backpressure under load when multiple channels
publish concurrently in gateway mode, where SDK callbacks blocking
on a full buffer can cause message loss or timeouts.
- Replace stdlib log.Printf with logger.InfoCF/WarnCF for consistency
with the rest of the codebase (addresses @nikolasdehor review point #3)
- ReleaseAll: clean refToScope/refs mappings even if refs entry is missing
- CleanExpired: guard refToScope lookup before scope cleanup
- Add TestReleaseAllCleansMappingsIfRefsMissing for robustness
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address review feedback from @alexhoshina and Codex:
- Log sendLoading errors in ticker goroutine instead of discarding
- Only start typing indicator when PlaceholderRecorder is available
to avoid wasted API calls and unnecessary goroutine creation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>