* fix(tools): close resp.Body on retry cancel and cache http.Client instances
Fix resp.Body leak in DoRequestWithRetry where req.Body (request) was
incorrectly closed instead of resp.Body (response) on context cancel.
Cache http.Client on web search/fetch provider structs and channel
adapters (WeCom, LINE) to avoid per-call allocation overhead.
* fix(channels): preserve original http client timeouts for LINE and WeCom
Split LINE single 60s client into infoClient (10s) for bot info lookups
and apiClient (30s) for messaging API calls. Lower WeCom cached client
base timeout from 60s to 30s (matching uploadMedia), and ensure it is
always >= the configured ReplyTimeout so the per-request context
deadline remains the effective limit.
* refactor(tools): extract timeout consts and deduplicate WebFetchTool constructors
Address PR review feedback from xiaket:
- Define searchTimeout, perplexityTimeout, fetchTimeout, defaultMaxChars,
and maxRedirects as package-level consts instead of magic numbers.
- Remove misleading "No proxy" comment in NewWebFetchTool.
- Deduplicate NewWebFetchTool by delegating to NewWebFetchToolWithProxy.
* test(utils): add context cancellation test for DoRequestWithRetry
Verify that resp.Body is properly closed when the context is canceled
during retry sleep, covering the C8 resp.Body leak fix.
* fix(utils): close resp in test to satisfy bodyclose linter
* fix(utils): eliminate flakiness in context cancellation retry test
Synchronize cancellation using an onRoundTrip callback from the
transport wrapper instead of a timing-based context timeout. This
ensures the first client.Do completes before cancel fires, so
cancellation always hits during sleepWithCtx.
Cancel the constructor-created context before overwriting in Start()
to prevent the original cancel function from becoming unreachable.
Move len(processedMsgs) check inside the write lock to eliminate a
data race, and re-insert the current msgID after map reset to prevent
duplicate processing of the in-flight message.
Applies to both WeComBotChannel and WeComAppChannel.
The ctx field was only set in Start(), so tests calling handleMessageCallback
without Start() caused a nil pointer dereference in MessageBus.PublishInbound.
The HTTP request context is canceled as soon as the handler returns the
response, causing PublishInbound to fail with "context canceled" when
processMessage runs asynchronously in a goroutine. Use the channel's
long-lived context (c.ctx) instead.
- Implement POSIX-specific gateway process management in gateway_posix.go
- Implement Windows-specific gateway process management in gateway_windows.go
- Create a menu system in menu.go for user interaction
- Develop model management functionality in model.go, including adding, deleting, and testing models
- Introduce a style configuration in style.go for consistent UI appearance
- Set up the main application entry point in main.go
- Update go.mod and go.sum to include necessary dependencies for tcell and tview
Add GOARM=6 targets for both picoclaw and picoclaw-launcher builds
to support older ARM devices like Raspberry Pi Zero/1.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
A standalone web-based tool for managing picoclaw configuration, OAuth
authentication providers, and gateway process lifecycle. Features include
a sidebar layout with i18n (en/zh) and theme support, real-time gateway
log streaming, startup prerequisites checks, and Windows icon embedding.
Co-authored-by: wj-xiao <meetwenjie@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- WhatsApp Start(): use deferred cleanup to nil out c.client/c.container
and disconnect/close resources on any error after struct fields are
assigned, preventing stale references and double-close in Stop()
- handleReasoning: treat bus.ErrBusClosed as an expected condition
(DEBUG level) alongside context timeout/cancel, avoiding WARN noise
during normal shutdown
- WhatsApp Send(): detect unpaired state (Store.ID == nil) and return
ErrTemporary instead of attempting to send while QR login is pending
- handleReasoning: check the returned error type (DeadlineExceeded /
Canceled) instead of ctx.Err() to decide log level, so pubCtx
timeouts on a full bus are correctly classified as expected
- Test: fill bus with a short-timeout loop instead of hardcoding the
buffer size (64), making the test resilient to buffer size changes
Move the stopping check and wg.Add(1) inside reconnectMu in
eventHandler, and set the stopping flag under the same lock in Stop().
This makes the two operations atomic with respect to each other,
preventing the race where:
1. eventHandler checks stopping (false)
2. Stop() sets stopping=true and enters wg.Wait() (wg is 0)
3. eventHandler calls wg.Add(1) → panic or goroutine leak
- Use c.runCtx for GetQRChannel so the QR producer is canceled on Stop()
- Add atomic stopping guard to prevent wg.Add/wg.Wait race in eventHandler
- Make Stop() context-aware: disconnect client before waiting, respect ctx deadline
- Reduce reasoning publish log noise: use debug level for expected ctx errors
- Add test for handleReasoning when outbound bus is full (timeout path)
Add a 5-second timeout to handleReasoning's PublishOutbound call so
fire-and-forget goroutines do not block indefinitely when the outbound
bus channel is full. Reasoning output is best-effort; on timeout the
publish is abandoned with a warning log instead of holding the
goroutine alive.
Fixes goroutine leak introduced in #802.
- 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.