Merge pull request #1360 from SocialNerd42069/fix/duplicate-assistant-texts
fix: prevent duplicate assistant texts from whitespace differences
This commit is contained in:
386
CHANGELOG.md
386
CHANGELOG.md
@@ -2,192 +2,226 @@
|
|||||||
|
|
||||||
Docs: https://docs.clawd.bot
|
Docs: https://docs.clawd.bot
|
||||||
|
|
||||||
## 2026.1.20
|
## 2026.1.19-3
|
||||||
|
|
||||||
### Highlights
|
|
||||||
- Nostr: add the Nostr channel plugin with profile management + onboarding defaults. (#1323) https://docs.clawd.bot/channels/nostr
|
|
||||||
- Gateway: add the OpenResponses-compatible `/v1/responses` endpoint. (#1229) https://docs.clawd.bot/gateway/openresponses-http-api
|
|
||||||
- Matrix: migrate to matrix-bot-sdk with E2EE support. (#1298) https://docs.clawd.bot/channels/matrix
|
|
||||||
- TUI: session picker shows derived titles, fuzzy search, relative times, and last message preview. (#1271) https://docs.clawd.bot/tui
|
|
||||||
- Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.clawd.bot/web/control-ui
|
|
||||||
- Memory: add native Gemini embeddings provider for memory search. (#1151) https://docs.clawd.bot/concepts/memory
|
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Commands: add `/allowlist` slash command for listing and editing channel allowlists.
|
|
||||||
- Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.clawd.bot/web/control-ui
|
|
||||||
- Control UI: drop the legacy list view. (#1345) https://docs.clawd.bot/web/control-ui
|
|
||||||
- TUI: add syntax highlighting for code blocks. (#1200) https://docs.clawd.bot/tui
|
|
||||||
- TUI: session picker shows derived titles, fuzzy search, relative times, and last message preview. (#1271) https://docs.clawd.bot/tui
|
|
||||||
- TUI: add a searchable model picker for quicker model selection. (#1198) https://docs.clawd.bot/tui
|
|
||||||
- TUI: add input history (up/down) for submitted messages. (#1348) https://docs.clawd.bot/tui
|
|
||||||
- ACP: add `clawdbot acp` for IDE integrations. https://docs.clawd.bot/cli/acp
|
|
||||||
- ACP: add `clawdbot acp client` interactive harness for debugging. https://docs.clawd.bot/cli/acp
|
|
||||||
- Skills: add download installs with OS-filtered options. https://docs.clawd.bot/tools/skills
|
|
||||||
- Skills: add the local sherpa-onnx-tts skill. https://docs.clawd.bot/tools/skills
|
|
||||||
- Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback. https://docs.clawd.bot/concepts/memory
|
|
||||||
- Memory: add SQLite embedding cache to speed up reindexing and frequent updates. https://docs.clawd.bot/concepts/memory
|
|
||||||
- Memory: add OpenAI batch indexing for embeddings when configured. https://docs.clawd.bot/concepts/memory
|
|
||||||
- Memory: enable OpenAI batch indexing by default for OpenAI embeddings. https://docs.clawd.bot/concepts/memory
|
|
||||||
- Memory: allow parallel OpenAI batch indexing jobs (default concurrency: 2). https://docs.clawd.bot/concepts/memory
|
|
||||||
- Memory: render progress immediately, color batch statuses in verbose logs, and poll OpenAI batch status every 2s by default. https://docs.clawd.bot/concepts/memory
|
|
||||||
- Memory: add `--verbose` logging for memory status + batch indexing details. https://docs.clawd.bot/concepts/memory
|
|
||||||
- Memory: add native Gemini embeddings provider for memory search. (#1151) https://docs.clawd.bot/concepts/memory
|
|
||||||
- Browser: allow config defaults for efficient snapshots in the tool/CLI. (#1336) https://docs.clawd.bot/tools/browser
|
|
||||||
- Nostr: add the Nostr channel plugin with profile management + onboarding defaults. (#1323) https://docs.clawd.bot/channels/nostr
|
|
||||||
- Matrix: migrate to matrix-bot-sdk with E2EE support, location handling, and group allowlist upgrades. (#1298) https://docs.clawd.bot/channels/matrix
|
|
||||||
- Slack: add HTTP webhook mode via Bolt HTTP receiver. (#1143) https://docs.clawd.bot/channels/slack
|
|
||||||
- Telegram: enrich forwarded-message context with normalized origin details + legacy fallback. (#1090) https://docs.clawd.bot/channels/telegram
|
|
||||||
- Discord: fall back to `/skill` when native command limits are exceeded. (#1287)
|
|
||||||
- Discord: expose `/skill` globally. (#1287)
|
|
||||||
- Zalouser: add channel dock metadata, config schema, setup wiring, probe, and status issues. (#1219) https://docs.clawd.bot/plugins/zalouser
|
|
||||||
- Plugins: require manifest-embedded config schemas with preflight validation warnings. (#1272) https://docs.clawd.bot/plugins/manifest
|
|
||||||
- Plugins: move channel catalog metadata into plugin manifests. (#1290) https://docs.clawd.bot/plugins/manifest
|
|
||||||
- Plugins: align Nextcloud Talk policy helpers with core patterns. (#1290) https://docs.clawd.bot/plugins/manifest
|
|
||||||
- Plugins/UI: let channel plugin metadata drive UI labels/icons and cron channel options. (#1306) https://docs.clawd.bot/web/control-ui
|
|
||||||
- Plugins: add plugin slots with a dedicated memory slot selector. https://docs.clawd.bot/plugins/agent-tools
|
|
||||||
- Plugins: ship the bundled BlueBubbles channel plugin (disabled by default). https://docs.clawd.bot/channels/bluebubbles
|
|
||||||
- Plugins: migrate bundled messaging extensions to the plugin SDK and resolve plugin-sdk imports in the loader.
|
|
||||||
- Plugins: migrate the Zalo plugin to the shared plugin SDK runtime. https://docs.clawd.bot/channels/zalo
|
|
||||||
- Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime. https://docs.clawd.bot/plugins/zalouser
|
|
||||||
- Plugins: allow optional agent tools with explicit allowlists and add the plugin tool authoring guide. https://docs.clawd.bot/plugins/agent-tools
|
|
||||||
- Plugins: auto-enable bundled channel/provider plugins when configuration is present.
|
|
||||||
- Plugins: sync plugin sources on channel switches and update npm-installed plugins during `clawdbot update`.
|
|
||||||
- Plugins: share npm plugin update logic between `clawdbot update` and `clawdbot plugins update`.
|
|
||||||
- Gateway/API: add `/v1/responses` (OpenResponses) with item-based input + semantic streaming events. (#1229)
|
|
||||||
- Gateway/API: expand `/v1/responses` to support file/image inputs, tool_choice, usage, and output limits. (#1229)
|
|
||||||
- Usage: add `/usage cost` summaries and macOS menu cost charts. https://docs.clawd.bot/reference/api-usage-costs
|
|
||||||
- Security: warn when <=300B models run without sandboxing while web tools are enabled. https://docs.clawd.bot/cli/security
|
|
||||||
- Exec: add host/security/ask routing for gateway + node exec. https://docs.clawd.bot/tools/exec
|
|
||||||
- Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node). https://docs.clawd.bot/tools/exec
|
|
||||||
- Exec approvals: migrate approvals to `~/.clawdbot/exec-approvals.json` with per-agent allowlists + skill auto-allow toggle, and add approvals UI + node exec lifecycle events. https://docs.clawd.bot/tools/exec-approvals
|
|
||||||
- Nodes: add headless node host (`clawdbot node start`) for `system.run`/`system.which`. https://docs.clawd.bot/cli/node
|
|
||||||
- Nodes: add node daemon service install/status/start/stop/restart. https://docs.clawd.bot/cli/node
|
|
||||||
- Bridge: add `skills.bins` RPC to support node host auto-allow skill bins.
|
|
||||||
- Sessions: add daily reset policy with per-type overrides and idle windows (default 4am local), preserving legacy idle-only configs. (#1146) https://docs.clawd.bot/concepts/session
|
|
||||||
- Sessions: allow `sessions_spawn` to override thinking level for sub-agent runs. https://docs.clawd.bot/tools/subagents
|
|
||||||
- Channels: unify thread/topic allowlist matching + command/mention gating helpers across core providers. https://docs.clawd.bot/concepts/groups
|
|
||||||
- Models: add Qwen Portal OAuth provider support. (#1120) https://docs.clawd.bot/providers/qwen
|
|
||||||
- Onboarding: add allowlist prompts and username-to-id resolution across core and extension channels. https://docs.clawd.bot/start/onboarding
|
|
||||||
- Docs: clarify allowlist input types and onboarding behavior for messaging channels. https://docs.clawd.bot/start/onboarding
|
|
||||||
- Docs: refresh Android node discovery docs for the Gateway WS service type. https://docs.clawd.bot/platforms/android
|
|
||||||
- Docs: surface Amazon Bedrock in provider lists and clarify Bedrock auth env vars. (#1289) https://docs.clawd.bot/bedrock
|
|
||||||
- Docs: clarify WhatsApp voice notes. https://docs.clawd.bot/channels/whatsapp
|
|
||||||
- Docs: clarify Windows WSL portproxy LAN access notes. https://docs.clawd.bot/platforms/windows
|
|
||||||
- Docs: refresh bird skill install metadata and usage notes. (#1302) https://docs.clawd.bot/tools/browser-login
|
|
||||||
- Agents: add local docs path resolution and include docs/mirror/source/community pointers in the system prompt.
|
|
||||||
- Agents: clarify node_modules read-only guidance in agent instructions.
|
|
||||||
- Config: stamp last-touched metadata on write and warn if the config is newer than the running build.
|
|
||||||
- macOS: hide usage section when usage is unavailable instead of showing provider errors.
|
|
||||||
- Android: migrate node transport to the Gateway WebSocket protocol with TLS pinning support + gateway discovery naming.
|
|
||||||
- Android: send structured payloads in node events/invokes and include user-agent metadata in gateway connects.
|
|
||||||
- Android: remove legacy bridge transport code now that nodes use the gateway protocol.
|
- Android: remove legacy bridge transport code now that nodes use the gateway protocol.
|
||||||
- Android: bump okhttp + dnsjava to satisfy lint dependency checks.
|
- Android: send structured payloads in node events/invokes and include user-agent metadata in gateway connects.
|
||||||
- Build: update workspace + core/plugin deps.
|
|
||||||
- Build: use tsgo for dev/watch builds by default (opt out with `CLAWDBOT_TS_COMPILER=tsc`).
|
### Fixes
|
||||||
- Repo: remove the Peekaboo git submodule now that the SPM release is used.
|
- Slack: respect verbose tool summaries and keep tool notifications threaded. (#1360) — thanks @SocialNerd42069.
|
||||||
- macOS: switch PeekabooBridge integration to the tagged Swift Package Manager release.
|
|
||||||
- macOS: stop syncing Peekaboo in postinstall.
|
## 2026.1.19-2
|
||||||
- Swabble: use the tagged Commander Swift package release.
|
|
||||||
|
### Changes
|
||||||
### Breaking
|
- Android: migrate node transport to the Gateway WebSocket protocol with TLS pinning support + gateway discovery naming.
|
||||||
- **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `clawdbot doctor --fix` to repair, then update plugins (`clawdbot plugins update`) if you use any.
|
- Android: bump okhttp + dnsjava to satisfy lint dependency checks.
|
||||||
|
- Docs: refresh Android node discovery docs for the Gateway WS service type.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Discovery: shorten Bonjour DNS-SD service type to `_clawdbot-gw._tcp` and update discovery clients/docs.
|
|
||||||
- Diagnostics: export OTLP logs, correct queue depth tracking, and document message-flow telemetry.
|
|
||||||
- Diagnostics: emit message-flow diagnostics across channels via shared dispatch. (#1244)
|
|
||||||
- Diagnostics: gate heartbeat/webhook logging. (#1244)
|
|
||||||
- Gateway: strip inbound envelope headers from chat history messages to keep clients clean.
|
|
||||||
- Gateway: clarify unauthorized handshake responses with token/password mismatch guidance.
|
|
||||||
- Gateway: allow mobile node client ids for iOS + Android handshake validation. (#1354)
|
|
||||||
- Gateway: clarify connect/validation errors for gateway params. (#1347)
|
|
||||||
- Gateway: preserve restart wake routing + thread replies across restarts. (#1337)
|
|
||||||
- Gateway: reschedule per-agent heartbeats on config hot reload without restarting the runner.
|
|
||||||
- Gateway: require authorized restarts for SIGUSR1 (restart/apply/update) so config gating can't be bypassed.
|
|
||||||
- Cron: auto-deliver isolated agent output to explicit targets without tool calls. (#1285)
|
|
||||||
- Agents: preserve subagent announce thread/topic routing + queued replies across channels. (#1241)
|
|
||||||
- Agents: propagate accountId into embedded runs so sub-agent announce routing honors the originating account. (#1058)
|
|
||||||
- Agents: avoid treating timeout errors with "aborted" messages as user aborts, so model fallback still runs. (#1137)
|
|
||||||
- Agents: sanitize oversized image payloads before send and surface image-dimension errors.
|
|
||||||
- Sessions: fall back to session labels when listing display names. (#1124)
|
|
||||||
- Compaction: include tool failure summaries in safeguard compaction to prevent retry loops. (#1084)
|
|
||||||
- Config: log invalid config issues once per run and keep invalid-config errors stackless.
|
|
||||||
- Config: allow Perplexity as a web_search provider in config validation. (#1230)
|
|
||||||
- Config: allow custom fields under `skills.entries.<name>.config` for skill credentials/config. (#1226)
|
|
||||||
- Doctor: clarify plugin auto-enable hint text in the startup banner.
|
|
||||||
- Doctor: canonicalize legacy session keys in session stores to prevent stale metadata. (#1169)
|
|
||||||
- Docs: make docs:list fail fast with a clear error if the docs directory is missing.
|
|
||||||
- Plugins: add Nextcloud Talk manifest for plugin config validation. (#1297)
|
|
||||||
- Plugins: surface plugin load/register/config errors in gateway logs with plugin/source context.
|
|
||||||
- CLI: preserve cron delivery settings when editing message payloads. (#1322)
|
|
||||||
- CLI: keep `clawdbot logs` output resilient to broken pipes while preserving progress output.
|
|
||||||
- CLI: avoid duplicating --profile/--dev flags when formatting commands.
|
|
||||||
- CLI: centralize CLI command registration to keep fast-path routing and program wiring in sync. (#1207)
|
|
||||||
- CLI: keep banners on routed commands, restore config guarding outside fast-path routing, and tighten fast-path flag parsing while skipping console capture for extra speed. (#1195)
|
|
||||||
- CLI: skip runner rebuilds when dist is fresh. (#1231)
|
|
||||||
- CLI: add WSL2/systemd unavailable hints in daemon status/doctor output.
|
|
||||||
- Status: route native `/status` to the active agent so model selection reflects the correct profile. (#1301)
|
|
||||||
- Status: show both usage windows with reset hints when usage data is available. (#1101)
|
|
||||||
- UI: keep config form enums typed, preserve empty strings, protect sensitive defaults, and deepen config search. (#1315)
|
|
||||||
- UI: preserve ordered list numbering in chat markdown. (#1341)
|
|
||||||
- UI: allow Control UI to read gatewayUrl from URL params for remote WebSocket targets. (#1342)
|
|
||||||
- UI: prevent double-scroll in Control UI chat by locking chat layout to the viewport. (#1283)
|
|
||||||
- UI: enable shell mode for sync Windows spawns to avoid `pnpm ui:build` EINVAL. (#1212)
|
|
||||||
- TUI: keep thinking blocks ordered before content during streaming and isolate per-run assembly. (#1202)
|
|
||||||
- TUI: align custom editor initialization with the latest pi-tui API. (#1298)
|
|
||||||
- TUI: show generic empty-state text for searchable pickers. (#1201)
|
|
||||||
- TUI: highlight model search matches and stabilize search ordering.
|
|
||||||
- Configure: hide OpenRouter auto routing model from the model picker. (#1182)
|
|
||||||
- Memory: show total file counts + scan issues in `clawdbot memory status`.
|
|
||||||
- Memory: fall back to non-batch embeddings after repeated batch failures.
|
|
||||||
- Memory: apply OpenAI batch defaults even without explicit remote config.
|
|
||||||
- Memory: index atomically so failed reindex preserves the previous memory database. (#1151)
|
|
||||||
- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151)
|
|
||||||
- Memory: retry transient 5xx errors (Cloudflare) during embedding indexing.
|
|
||||||
- Memory: parallelize embedding indexing with rate-limit retries.
|
|
||||||
- Memory: split overly long lines to keep embeddings under token limits.
|
|
||||||
- Memory: skip empty chunks to avoid invalid embedding inputs.
|
|
||||||
- Memory: split embedding batches to avoid OpenAI token limits during indexing.
|
|
||||||
- Memory: probe sqlite-vec availability in `clawdbot memory status`.
|
|
||||||
- Exec approvals: enforce allowlist when ask is off.
|
|
||||||
- Exec approvals: prefer raw command for node approvals/events.
|
|
||||||
- Tools: show exec elevated flag before the command and keep it outside markdown in tool summaries.
|
|
||||||
- Tools: return a companion-app-required message when node exec is requested with no paired node.
|
|
||||||
- Tools: return a companion-app-required message when `system.run` is requested without a supporting node.
|
|
||||||
- Exec: default gateway/node exec security to allowlist when unset (sandbox stays deny).
|
|
||||||
- Exec: prefer bash when fish is default shell, falling back to sh if bash is missing. (#1297)
|
|
||||||
- Exec: merge login-shell PATH for host=gateway exec while keeping daemon PATH minimal. (#1304)
|
|
||||||
- Streaming: emit assistant deltas for OpenAI-compatible SSE chunks. (#1147)
|
|
||||||
- Discord: make resolve warnings avoid raw JSON payloads on rate limits.
|
|
||||||
- Discord: process message handlers in parallel across sessions to avoid event queue blocking. (#1295)
|
|
||||||
- Discord: stop reconnecting the gateway after aborts to prevent duplicate listeners.
|
|
||||||
- Discord: only emit slow listener warnings after 30s.
|
|
||||||
- Discord: inherit parent channel allowlists for thread slash commands and reactions. (#1123)
|
|
||||||
- Telegram: honor pairing allowlists for native slash commands.
|
|
||||||
- Telegram: preserve hidden text_link URLs by expanding entities in inbound text. (#1118)
|
|
||||||
- Slack: resolve Bolt import interop for Bun + Node. (#1191)
|
|
||||||
- Web search: infer Perplexity base URL from API key source (direct vs OpenRouter).
|
|
||||||
- Web fetch: harden SSRF protection with shared hostname checks and redirect limits. (#1346)
|
|
||||||
- Browser: register AI snapshot refs for act commands. (#1282)
|
|
||||||
- Voice call: include request query in Twilio webhook verification when publicUrl is set. (#864)
|
|
||||||
- Anthropic: default API prompt caching to 1h with configurable TTL override.
|
|
||||||
- Anthropic: ignore TTL for OAuth.
|
|
||||||
- Auth profiles: keep auto-pinned preference while allowing rotation on failover. (#1138)
|
|
||||||
- Auth profiles: user pins stay locked. (#1138)
|
|
||||||
- Model catalog: avoid caching import failures, log transient discovery errors, and keep partial results. (#1332)
|
|
||||||
- Tests: stabilize Windows gateway/CLI tests by skipping sidecars, normalizing argv, and extending timeouts.
|
- Tests: stabilize Windows gateway/CLI tests by skipping sidecars, normalizing argv, and extending timeouts.
|
||||||
- Tests: stabilize plugin SDK resolution and embedded agent timeouts.
|
- CLI: skip runner rebuilds when dist is fresh. (#1231) — thanks @mukhtharcm, @thewilloftheshadow.
|
||||||
- Windows: install gateway scheduled task as the current user.
|
|
||||||
- Windows: show friendly guidance instead of failing on access denied.
|
## 2026.1.19-1
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
- **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety; run `clawdbot doctor --fix` to repair.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Usage: add `/usage cost` summaries and macOS menu cost submenu with daily charting.
|
||||||
|
- Agents: clarify node_modules read-only guidance in agent instructions.
|
||||||
|
- TUI: add syntax highlighting for code blocks. (#1200) — thanks @vignesh07.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- UI: enable shell mode for sync Windows spawns to avoid `pnpm ui:build` EINVAL. (#1212) — thanks @longmaba.
|
||||||
|
- Agents: add `clawdbot agents set-identity` helper and update bootstrap guidance for multi-agent setups. (#1222) — thanks @ThePickle31.
|
||||||
|
- Plugins: surface plugin load/register/config errors in gateway logs with plugin/source context.
|
||||||
|
- Agents: propagate accountId into embedded runs so sub-agent announce routing honors the originating account. (#1058)
|
||||||
|
- Compaction: include tool failure summaries in safeguard compaction to prevent retry loops. (#1084)
|
||||||
|
- Daemon: include HOME in service environments to avoid missing HOME errors. (#1214) — thanks @ameno-.
|
||||||
|
- TUI: show generic empty-state text for searchable pickers. (#1201) — thanks @vignesh07.
|
||||||
|
- Doctor: canonicalize legacy session keys in session stores to prevent stale metadata. (#1169)
|
||||||
|
- CLI: centralize CLI command registration to keep fast-path routing and program wiring in sync. (#1207) — thanks @gumadeiras.
|
||||||
|
|
||||||
|
## 2026.1.18-5
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Dependencies: update core + plugin deps (grammy, vitest, openai, Microsoft agents hosting, etc.).
|
||||||
|
- Onboarding: add allowlist prompts and username-to-id resolution across core and extension channels.
|
||||||
|
- TUI: add searchable model picker for quicker model selection. (#1198) — thanks @vignesh07.
|
||||||
|
- Docs: clarify allowlist input types and onboarding behavior for messaging channels.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Configure: hide OpenRouter auto routing model from the model picker. (#1182) — thanks @zerone0x.
|
||||||
|
- Docs: make docs:list fail fast with a clear error if the docs directory is missing.
|
||||||
- macOS: load menu session previews asynchronously so items populate while the menu is open.
|
- macOS: load menu session previews asynchronously so items populate while the menu is open.
|
||||||
- macOS: use label colors for session preview text so previews render in menu subviews.
|
- macOS: use label colors for session preview text so previews render in menu subviews.
|
||||||
- macOS: suppress usage error text in the menubar cost view.
|
- macOS: suppress usage error text in the menubar cost view.
|
||||||
- macOS: Doctor repairs LaunchAgent bootstrap issues for Gateway + Node when listed but not loaded. (#1166)
|
- Telegram: honor pairing allowlists for native slash commands.
|
||||||
- macOS: avoid touching launchd in Remote over SSH so quitting the app no longer disables the remote gateway. (#1105)
|
- TUI: highlight model search matches and stabilize search ordering.
|
||||||
- macOS: bundle Textual resources in packaged app builds to avoid code block crashes. (#1006)
|
- CLI: keep banners on routed commands, restore config guarding outside fast-path routing, and tighten fast-path flag parsing while skipping console capture for extra speed. (#1195) — thanks @gumadeiras.
|
||||||
- Daemon: include HOME in service environments to avoid missing HOME errors. (#1214)
|
- Slack: resolve Bolt import interop for Bun + Node. (#1191) — thanks @CoreyH.
|
||||||
|
- Gateway: require authorized restarts for SIGUSR1 (restart/apply/update) so config gating can't be bypassed.
|
||||||
|
- Discord: stop reconnecting the gateway after aborts to prevent duplicate listeners.
|
||||||
|
|
||||||
Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @NicholaiVogel, @RyanLisse, @ThePickle31, @VACInc, @Whoaa512, @YuriNachos, @aaronveklabs, @abdaraxus, @alauppe, @ameno-, @artuskg, @austinm911, @bradleypriest, @cheeeee, @dougvk, @fogboots, @gnarco, @gumadeiras, @jdrhyne, @joelklabo, @longmaba, @mukhtharcm, @odysseus0, @oscargavin, @rhjoh, @sebslight, @sibbl, @sleontenko, @steipete, @suminhthanh, @thewilloftheshadow, @tyler6204, @vignesh07, @visionik, @ysqander, @zerone0x.
|
## 2026.1.18-4
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- macOS: switch PeekabooBridge integration to the tagged Swift Package Manager release (no submodule).
|
||||||
|
- macOS: stop syncing Peekaboo as a git submodule in postinstall.
|
||||||
|
- Swabble: use the tagged Commander Swift package release.
|
||||||
|
- CLI: add `clawdbot acp client` interactive ACP harness for debugging.
|
||||||
|
- Plugins: route command detection/text chunking helpers through the plugin runtime and drop runtime exports from the SDK.
|
||||||
|
- Plugins: auto-enable bundled channel/provider plugins when configuration is present.
|
||||||
|
- Config: stamp last-touched metadata on write and warn if the config is newer than the running build.
|
||||||
|
- macOS: hide usage section when usage is unavailable instead of showing provider errors.
|
||||||
|
- Memory: add native Gemini embeddings provider for memory search. (#1151)
|
||||||
|
- Agents: add local docs path resolution and include docs/mirror/source/community pointers in the system prompt.
|
||||||
|
- Slack: add HTTP webhook mode via Bolt HTTP receiver for Events API deployments. (#1143) — thanks @jdrhyne.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Auth profiles: keep auto-pinned preference while allowing rotation on failover; user pins stay locked. (#1138) — thanks @cheeeee.
|
||||||
|
- Agents: sanitize oversized image payloads before send and surface image-dimension errors.
|
||||||
|
- macOS: Doctor repairs LaunchAgent bootstrap issues for Gateway + Node when listed but not loaded. (#1166) — thanks @AlexMikhalev.
|
||||||
|
- macOS: avoid touching launchd in Remote over SSH so quitting the app no longer disables the remote gateway. (#1105)
|
||||||
|
- Memory: index atomically so failed reindex preserves the previous memory database. (#1151)
|
||||||
|
- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151)
|
||||||
|
|
||||||
|
## 2026.1.18-3
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Exec: add host/security/ask routing for gateway + node exec.
|
||||||
|
- Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node).
|
||||||
|
- macOS: migrate exec approvals to `~/.clawdbot/exec-approvals.json` with per-agent allowlists and skill auto-allow toggle.
|
||||||
|
- macOS: add approvals socket UI server + node exec lifecycle events.
|
||||||
|
- Nodes: add headless node host (`clawdbot node start`) for `system.run`/`system.which`.
|
||||||
|
- Nodes: add node daemon service install/status/start/stop/restart.
|
||||||
|
- Bridge: add `skills.bins` RPC to support node host auto-allow skill bins.
|
||||||
|
- Slash commands: replace `/cost` with `/usage off|tokens|full` to control per-response usage footer; `/usage` no longer aliases `/status`. (Supersedes #1140) — thanks @Nachx639.
|
||||||
|
- Sessions: add daily reset policy with per-type overrides and idle windows (default 4am local), preserving legacy idle-only configs. (#1146) — thanks @austinm911.
|
||||||
|
- Agents: auto-inject local image references for vision models and avoid reloading history images. (#1098) — thanks @tyler6204.
|
||||||
|
- Docs: refresh exec/elevated/exec-approvals docs for the new flow. https://docs.clawd.bot/tools/exec-approvals
|
||||||
|
- Docs: add node host CLI + update exec approvals/bridge protocol docs. https://docs.clawd.bot/cli/node
|
||||||
|
- ACP: add experimental ACP support for IDE integrations (`clawdbot acp`). Thanks @visionik.
|
||||||
|
- Tools: allow `sessions_spawn` to override thinking level for sub-agent runs.
|
||||||
|
- Channels: unify thread/topic allowlist matching + command/mention gating helpers across core providers.
|
||||||
|
- Models: add Qwen Portal OAuth provider support. (#1120) — thanks @mukhtharcm.
|
||||||
|
- Memory: add `--verbose` logging for memory status + batch indexing details.
|
||||||
|
- Memory: allow parallel OpenAI batch indexing jobs (default concurrency: 2).
|
||||||
|
- macOS: add per-agent exec approvals with allowlists, skill CLI auto-allow, and settings UI.
|
||||||
|
- Docs: add exec approvals guide and link from tools index. https://docs.clawd.bot/tools/exec-approvals
|
||||||
|
- macOS: add exec-host IPC for node service `system.run` with HMAC + peer UID checks.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Exec approvals: enforce allowlist when ask is off; prefer raw command for node approvals/events.
|
||||||
|
- Tools: return a companion-app-required message when node exec is requested with no paired node.
|
||||||
|
- Streaming: emit assistant deltas for OpenAI-compatible SSE chunks. (#1147) — thanks @alauppe.
|
||||||
|
- Model fallback: treat timeout aborts as failover while preserving user aborts. (#1137) — thanks @cheeeee.
|
||||||
|
|
||||||
|
## 2026.1.18-2
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Tests: stabilize plugin SDK resolution and embedded agent timeouts.
|
||||||
|
|
||||||
|
## 2026.1.18-1
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Tools: allow `sessions_spawn` to override thinking level for sub-agent runs.
|
||||||
|
- Channels: unify thread/topic allowlist matching + command/mention gating helpers across core providers.
|
||||||
|
- Models: add Qwen Portal OAuth provider support. (#1120) — thanks @mukhtharcm.
|
||||||
|
- Memory: add `--verbose` logging for memory status + batch indexing details.
|
||||||
|
- Memory: allow parallel OpenAI batch indexing jobs (default concurrency: 2).
|
||||||
|
- macOS: add per-agent exec approvals with allowlists, skill CLI auto-allow, and settings UI.
|
||||||
|
- Docs: add exec approvals guide and link from tools index. https://docs.clawd.bot/tools/exec-approvals
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Memory: apply OpenAI batch defaults even without explicit remote config.
|
||||||
|
- macOS: bundle Textual resources in packaged app builds to avoid code block crashes. (#1006)
|
||||||
|
- Tools: return a companion-app-required message when `system.run` is requested without a supporting node.
|
||||||
|
- Discord: only emit slow listener warnings after 30s.
|
||||||
|
|
||||||
|
## 2026.1.17-6
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Plugins: add exclusive plugin slots with a dedicated memory slot selector.
|
||||||
|
- Memory: ship core memory tools + CLI as the bundled `memory-core` plugin.
|
||||||
|
- Docs: document plugin slots and memory plugin behavior.
|
||||||
|
- Plugins: add the bundled BlueBubbles channel plugin (disabled by default).
|
||||||
|
- Plugins: migrate bundled messaging extensions to the plugin SDK; resolve plugin-sdk imports in loader.
|
||||||
|
- Plugins: migrate the Zalo plugin to the shared plugin SDK runtime.
|
||||||
|
- Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime.
|
||||||
|
|
||||||
|
## 2026.1.17-5
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback.
|
||||||
|
- Memory: add SQLite embedding cache to speed up reindexing and frequent updates.
|
||||||
|
- CLI: surface FTS + embedding cache state in `clawdbot memory status`.
|
||||||
|
- Memory: render progress immediately, color batch statuses in verbose logs, and poll OpenAI batch status every 2s by default.
|
||||||
|
- Plugins: allow optional agent tools with explicit allowlists and add plugin tool authoring guide. https://docs.clawd.bot/plugins/agent-tools
|
||||||
|
- Tools: centralize plugin tool policy helpers.
|
||||||
|
- Commands: add `/subagents info` and show sub-agent counts in `/status`.
|
||||||
|
- Docs: clarify plugin agent tool configuration. https://docs.clawd.bot/plugins/agent-tools
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Voice call: include request query in Twilio webhook verification when publicUrl is set. (#864)
|
||||||
|
|
||||||
|
## 2026.1.18-1
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Tools: allow `sessions_spawn` to override thinking level for sub-agent runs.
|
||||||
|
- Channels: unify thread/topic allowlist matching + command/mention gating helpers across core providers.
|
||||||
|
- Models: add Qwen Portal OAuth provider support. (#1120) — thanks @mukhtharcm.
|
||||||
|
- Memory: add `--verbose` logging for memory status + batch indexing details.
|
||||||
|
- Memory: allow parallel OpenAI batch indexing jobs (default concurrency: 2).
|
||||||
|
- macOS: add per-agent exec approvals with allowlists, skill CLI auto-allow, and settings UI.
|
||||||
|
- Docs: add exec approvals guide and link from tools index. https://docs.clawd.bot/tools/exec-approvals
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Memory: apply OpenAI batch defaults even without explicit remote config.
|
||||||
|
- macOS: bundle Textual resources in packaged app builds to avoid code block crashes. (#1006)
|
||||||
|
- Tools: return a companion-app-required message when `system.run` is requested without a supporting node.
|
||||||
|
- Discord: only emit slow listener warnings after 30s.
|
||||||
|
## 2026.1.17-3
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Memory: add OpenAI Batch API indexing for embeddings when configured.
|
||||||
|
- Memory: enable OpenAI batch indexing by default for OpenAI embeddings.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Memory: retry transient 5xx errors (Cloudflare) during embedding indexing.
|
||||||
|
|
||||||
|
## 2026.1.17-2
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Tools: show exec elevated flag before the command and keep it outside markdown in tool summaries.
|
||||||
|
- Memory: parallelize embedding indexing with rate-limit retries.
|
||||||
|
- Memory: split overly long lines to keep embeddings under token limits.
|
||||||
|
- Memory: skip empty chunks to avoid invalid embedding inputs.
|
||||||
|
- Sessions: fall back to session labels when listing display names. (#1124) — thanks @abdaraxus.
|
||||||
|
- Discord: inherit parent channel allowlists for thread slash commands and reactions. (#1123) — thanks @thewilloftheshadow.
|
||||||
|
|
||||||
|
## 2026.1.17-1
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Telegram: enrich forwarded message context with normalized origin details + legacy fallback. (#1090) — thanks @sleontenko.
|
||||||
|
- macOS: strip prerelease/build suffixes when parsing gateway semver patches. (#1110) — thanks @zerone0x.
|
||||||
|
- macOS: keep CLI install pinned to the full build suffix. (#1111) — thanks @artuskg.
|
||||||
|
- CLI: surface update availability in `clawdbot status`.
|
||||||
|
- CLI: add `clawdbot memory status --deep/--index` probes.
|
||||||
|
- CLI: add playful update completion quips.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Doctor: avoid re-adding WhatsApp ack reaction config when only legacy auth files exist. (#1087) — thanks @YuriNachos.
|
||||||
|
- Hooks: parse multi-line/YAML frontmatter metadata blocks (JSON5-friendly). (#1114) — thanks @sebslight.
|
||||||
|
- CLI: add WSL2/systemd unavailable hints in daemon status/doctor output.
|
||||||
|
- Windows: install gateway scheduled task as the current user; show friendly guidance instead of failing on access denied.
|
||||||
|
- Status: show both usage windows with reset hints when usage data is available. (#1101) — thanks @rhjoh.
|
||||||
|
- Memory: probe sqlite-vec availability in `clawdbot memory status`.
|
||||||
|
- Memory: split embedding batches to avoid OpenAI token limits during indexing.
|
||||||
|
- Telegram: preserve hidden text_link URLs by expanding entities in inbound text. (#1118) — thanks @sleontenko.
|
||||||
|
|
||||||
## 2026.1.16-2
|
## 2026.1.16-2
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ bash pty:true workdir:~/project background:true command:"codex exec --full-auto
|
|||||||
# Monitor progress
|
# Monitor progress
|
||||||
process action:log sessionId:XXX
|
process action:log sessionId:XXX
|
||||||
|
|
||||||
# Check if done
|
# Check if done
|
||||||
process action:poll sessionId:XXX
|
process action:poll sessionId:XXX
|
||||||
|
|
||||||
# Send input (if agent asks a question)
|
# Send input (if agent asks a question)
|
||||||
@@ -217,15 +217,55 @@ git worktree remove /tmp/issue-99
|
|||||||
|
|
||||||
## ⚠️ Rules
|
## ⚠️ Rules
|
||||||
|
|
||||||
1. **Always use pty:true** — coding agents need a terminal!
|
1. **Always use pty:true** - coding agents need a terminal!
|
||||||
2. **Respect tool choice** — if user asks for Codex, use Codex. NEVER offer to build it yourself!
|
2. **Respect tool choice** - if user asks for Codex, use Codex.
|
||||||
3. **Be patient** — don't kill sessions because they're "slow"
|
- Orchestrator mode: do NOT hand-code patches yourself.
|
||||||
4. **Monitor with process:log** — check progress without interfering
|
- If an agent fails/hangs, respawn it or ask the user for direction, but don't silently take over.
|
||||||
5. **--full-auto for building** — auto-approves changes
|
3. **Be patient** - don't kill sessions because they're "slow"
|
||||||
6. **vanilla for reviewing** — no special flags needed
|
4. **Monitor with process:log** - check progress without interfering
|
||||||
7. **Parallel is OK** — run many Codex processes at once for batch work
|
5. **--full-auto for building** - auto-approves changes
|
||||||
8. **NEVER start Codex in ~/clawd/** — it'll read your soul docs and get weird ideas about the org chart!
|
6. **vanilla for reviewing** - no special flags needed
|
||||||
9. **NEVER checkout branches in ~/Projects/clawdbot/** — that's the LIVE Clawdbot instance!
|
7. **Parallel is OK** - run many Codex processes at once for batch work
|
||||||
|
8. **NEVER start Codex in ~/clawd/** - it'll read your soul docs and get weird ideas about the org chart!
|
||||||
|
9. **NEVER checkout branches in ~/Projects/clawdbot/** - that's the LIVE Clawdbot instance!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Progress Updates (Critical)
|
||||||
|
|
||||||
|
When you spawn coding agents in the background, keep the user in the loop.
|
||||||
|
|
||||||
|
- Send 1 short message when you start (what's running + where).
|
||||||
|
- Then only update again when something changes:
|
||||||
|
- a milestone completes (build finished, tests passed)
|
||||||
|
- the agent asks a question / needs input
|
||||||
|
- you hit an error or need user action
|
||||||
|
- the agent finishes (include what changed + where)
|
||||||
|
- If you kill a session, immediately say you killed it and why.
|
||||||
|
|
||||||
|
This prevents the user from seeing only "Agent failed before reply" and having no idea what happened.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auto-Notify on Completion
|
||||||
|
|
||||||
|
For long-running background tasks, append a wake trigger to your prompt so Clawdbot gets notified immediately when the agent finishes (instead of waiting for the next heartbeat):
|
||||||
|
|
||||||
|
```
|
||||||
|
... your task here.
|
||||||
|
|
||||||
|
When completely finished, run this command to notify me:
|
||||||
|
clawdbot gateway wake --text "Done: [brief summary of what was built]" --mode now
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
bash pty:true workdir:~/project background:true command:"codex --yolo exec 'Build a REST API for todos.
|
||||||
|
|
||||||
|
When completely finished, run: clawdbot gateway wake --text \"Done: Built todos REST API with CRUD endpoints\" --mode now'"
|
||||||
|
```
|
||||||
|
|
||||||
|
This triggers an immediate wake event — Skippy gets pinged in seconds, not 10 minutes.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ export type EmbeddedPiSubscribeState = {
|
|||||||
lastStreamedAssistant?: string;
|
lastStreamedAssistant?: string;
|
||||||
lastStreamedReasoning?: string;
|
lastStreamedReasoning?: string;
|
||||||
lastBlockReplyText?: string;
|
lastBlockReplyText?: string;
|
||||||
|
assistantMessageIndex: number;
|
||||||
|
lastAssistantTextMessageIndex: number;
|
||||||
|
lastAssistantTextNormalized?: string;
|
||||||
|
lastAssistantTextTrimmed?: string;
|
||||||
assistantTextBaseline: number;
|
assistantTextBaseline: number;
|
||||||
suppressBlockChunks: boolean;
|
suppressBlockChunks: boolean;
|
||||||
lastReasoningSent?: string;
|
lastReasoningSent?: string;
|
||||||
|
|||||||
@@ -86,6 +86,35 @@ describe("subscribeEmbeddedPiSession", () => {
|
|||||||
|
|
||||||
expect(subscription.assistantTexts).toEqual(["Hello world"]);
|
expect(subscription.assistantTexts).toEqual(["Hello world"]);
|
||||||
});
|
});
|
||||||
|
it("does not duplicate assistantTexts when message_end repeats with trailing whitespace changes", () => {
|
||||||
|
let handler: SessionEventHandler | undefined;
|
||||||
|
const session: StubSession = {
|
||||||
|
subscribe: (fn) => {
|
||||||
|
handler = fn;
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = subscribeEmbeddedPiSession({
|
||||||
|
session: session as unknown as Parameters<typeof subscribeEmbeddedPiSession>[0]["session"],
|
||||||
|
runId: "run",
|
||||||
|
});
|
||||||
|
|
||||||
|
const assistantMessageWithNewline = {
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: "Hello world\n" }],
|
||||||
|
} as AssistantMessage;
|
||||||
|
|
||||||
|
const assistantMessageTrimmed = {
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: "Hello world" }],
|
||||||
|
} as AssistantMessage;
|
||||||
|
|
||||||
|
handler?.({ type: "message_end", message: assistantMessageWithNewline });
|
||||||
|
handler?.({ type: "message_end", message: assistantMessageTrimmed });
|
||||||
|
|
||||||
|
expect(subscription.assistantTexts).toEqual(["Hello world"]);
|
||||||
|
});
|
||||||
it("does not duplicate assistantTexts when message_end repeats with reasoning blocks", () => {
|
it("does not duplicate assistantTexts when message_end repeats with reasoning blocks", () => {
|
||||||
let handler: SessionEventHandler | undefined;
|
let handler: SessionEventHandler | undefined;
|
||||||
const session: StubSession = {
|
const session: StubSession = {
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
lastStreamedAssistant: undefined,
|
lastStreamedAssistant: undefined,
|
||||||
lastStreamedReasoning: undefined,
|
lastStreamedReasoning: undefined,
|
||||||
lastBlockReplyText: undefined,
|
lastBlockReplyText: undefined,
|
||||||
|
assistantMessageIndex: 0,
|
||||||
|
lastAssistantTextMessageIndex: -1,
|
||||||
|
lastAssistantTextNormalized: undefined,
|
||||||
|
lastAssistantTextTrimmed: undefined,
|
||||||
assistantTextBaseline: 0,
|
assistantTextBaseline: 0,
|
||||||
suppressBlockChunks: false, // Avoid late chunk inserts after final text merge.
|
suppressBlockChunks: false, // Avoid late chunk inserts after final text merge.
|
||||||
lastReasoningSent: undefined,
|
lastReasoningSent: undefined,
|
||||||
@@ -84,9 +88,36 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
state.lastStreamedReasoning = undefined;
|
state.lastStreamedReasoning = undefined;
|
||||||
state.lastReasoningSent = undefined;
|
state.lastReasoningSent = undefined;
|
||||||
state.suppressBlockChunks = false;
|
state.suppressBlockChunks = false;
|
||||||
|
state.assistantMessageIndex += 1;
|
||||||
|
state.lastAssistantTextMessageIndex = -1;
|
||||||
|
state.lastAssistantTextNormalized = undefined;
|
||||||
|
state.lastAssistantTextTrimmed = undefined;
|
||||||
state.assistantTextBaseline = nextAssistantTextBaseline;
|
state.assistantTextBaseline = nextAssistantTextBaseline;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rememberAssistantText = (text: string) => {
|
||||||
|
state.lastAssistantTextMessageIndex = state.assistantMessageIndex;
|
||||||
|
state.lastAssistantTextTrimmed = text.trimEnd();
|
||||||
|
const normalized = normalizeTextForComparison(text);
|
||||||
|
state.lastAssistantTextNormalized = normalized.length > 0 ? normalized : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldSkipAssistantText = (text: string) => {
|
||||||
|
if (state.lastAssistantTextMessageIndex !== state.assistantMessageIndex) return false;
|
||||||
|
const trimmed = text.trimEnd();
|
||||||
|
if (trimmed && trimmed === state.lastAssistantTextTrimmed) return true;
|
||||||
|
const normalized = normalizeTextForComparison(text);
|
||||||
|
if (normalized.length > 0 && normalized === state.lastAssistantTextNormalized) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pushAssistantText = (text: string) => {
|
||||||
|
if (!text) return;
|
||||||
|
if (shouldSkipAssistantText(text)) return;
|
||||||
|
assistantTexts.push(text);
|
||||||
|
rememberAssistantText(text);
|
||||||
|
};
|
||||||
|
|
||||||
const finalizeAssistantTexts = (args: {
|
const finalizeAssistantTexts = (args: {
|
||||||
text: string;
|
text: string;
|
||||||
addedDuringMessage: boolean;
|
addedDuringMessage: boolean;
|
||||||
@@ -103,16 +134,15 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
assistantTexts.length - state.assistantTextBaseline,
|
assistantTexts.length - state.assistantTextBaseline,
|
||||||
text,
|
text,
|
||||||
);
|
);
|
||||||
|
rememberAssistantText(text);
|
||||||
} else {
|
} else {
|
||||||
const last = assistantTexts.at(-1);
|
pushAssistantText(text);
|
||||||
if (!last || last !== text) assistantTexts.push(text);
|
|
||||||
}
|
}
|
||||||
state.suppressBlockChunks = true;
|
state.suppressBlockChunks = true;
|
||||||
} else if (!addedDuringMessage && !chunkerHasBuffered && text) {
|
} else if (!addedDuringMessage && !chunkerHasBuffered && text) {
|
||||||
// Non-streaming models (no text_delta): ensure assistantTexts gets the final
|
// Non-streaming models (no text_delta): ensure assistantTexts gets the final
|
||||||
// text when the chunker has nothing buffered to drain.
|
// text when the chunker has nothing buffered to drain.
|
||||||
const last = assistantTexts.at(-1);
|
pushAssistantText(text);
|
||||||
if (!last || last !== text) assistantTexts.push(text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.assistantTextBaseline = assistantTexts.length;
|
state.assistantTextBaseline = assistantTexts.length;
|
||||||
@@ -338,8 +368,11 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldSkipAssistantText(chunk)) return;
|
||||||
|
|
||||||
state.lastBlockReplyText = chunk;
|
state.lastBlockReplyText = chunk;
|
||||||
assistantTexts.push(chunk);
|
assistantTexts.push(chunk);
|
||||||
|
rememberAssistantText(chunk);
|
||||||
if (!params.onBlockReply) return;
|
if (!params.onBlockReply) return;
|
||||||
const splitResult = parseReplyDirectives(chunk);
|
const splitResult = parseReplyDirectives(chunk);
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -16,19 +16,21 @@ export const createShouldEmitToolResult = (params: {
|
|||||||
storePath?: string;
|
storePath?: string;
|
||||||
resolvedVerboseLevel: VerboseLevel;
|
resolvedVerboseLevel: VerboseLevel;
|
||||||
}): (() => boolean) => {
|
}): (() => boolean) => {
|
||||||
|
// Normalize verbose values from session store/config so false/"false" still means off.
|
||||||
|
const fallbackVerbose = normalizeVerboseLevel(String(params.resolvedVerboseLevel ?? "")) ?? "off";
|
||||||
return () => {
|
return () => {
|
||||||
if (!params.sessionKey || !params.storePath) {
|
if (!params.sessionKey || !params.storePath) {
|
||||||
return params.resolvedVerboseLevel !== "off";
|
return fallbackVerbose !== "off";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const store = loadSessionStore(params.storePath);
|
const store = loadSessionStore(params.storePath);
|
||||||
const entry = store[params.sessionKey];
|
const entry = store[params.sessionKey];
|
||||||
const current = normalizeVerboseLevel(entry?.verboseLevel);
|
const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? ""));
|
||||||
if (current) return current !== "off";
|
if (current) return current !== "off";
|
||||||
} catch {
|
} catch {
|
||||||
// ignore store read failures
|
// ignore store read failures
|
||||||
}
|
}
|
||||||
return params.resolvedVerboseLevel !== "off";
|
return fallbackVerbose !== "off";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,19 +39,21 @@ export const createShouldEmitToolOutput = (params: {
|
|||||||
storePath?: string;
|
storePath?: string;
|
||||||
resolvedVerboseLevel: VerboseLevel;
|
resolvedVerboseLevel: VerboseLevel;
|
||||||
}): (() => boolean) => {
|
}): (() => boolean) => {
|
||||||
|
// Normalize verbose values from session store/config so false/"false" still means off.
|
||||||
|
const fallbackVerbose = normalizeVerboseLevel(String(params.resolvedVerboseLevel ?? "")) ?? "off";
|
||||||
return () => {
|
return () => {
|
||||||
if (!params.sessionKey || !params.storePath) {
|
if (!params.sessionKey || !params.storePath) {
|
||||||
return params.resolvedVerboseLevel === "full";
|
return fallbackVerbose === "full";
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const store = loadSessionStore(params.storePath);
|
const store = loadSessionStore(params.storePath);
|
||||||
const entry = store[params.sessionKey];
|
const entry = store[params.sessionKey];
|
||||||
const current = normalizeVerboseLevel(entry?.verboseLevel);
|
const current = normalizeVerboseLevel(String(entry?.verboseLevel ?? ""));
|
||||||
if (current) return current === "full";
|
if (current) return current === "full";
|
||||||
} catch {
|
} catch {
|
||||||
// ignore store read failures
|
// ignore store read failures
|
||||||
}
|
}
|
||||||
return params.resolvedVerboseLevel === "full";
|
return fallbackVerbose === "full";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -209,6 +209,22 @@ describe("routeReply", () => {
|
|||||||
expect(mocks.sendMessageSlack).toHaveBeenCalledWith("channel:C123", "hi", expect.any(Object));
|
expect(mocks.sendMessageSlack).toHaveBeenCalledWith("channel:C123", "hi", expect.any(Object));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses threadId for Slack when replyToId is missing", async () => {
|
||||||
|
mocks.sendMessageSlack.mockClear();
|
||||||
|
await routeReply({
|
||||||
|
payload: { text: "hi" },
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C123",
|
||||||
|
threadId: "456.789",
|
||||||
|
cfg: {} as never,
|
||||||
|
});
|
||||||
|
expect(mocks.sendMessageSlack).toHaveBeenCalledWith(
|
||||||
|
"channel:C123",
|
||||||
|
"hi",
|
||||||
|
expect.objectContaining({ threadTs: "456.789" }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("passes thread id to Telegram sends", async () => {
|
it("passes thread id to Telegram sends", async () => {
|
||||||
mocks.sendMessageTelegram.mockClear();
|
mocks.sendMessageTelegram.mockClear();
|
||||||
await routeReply({
|
await routeReply({
|
||||||
|
|||||||
@@ -5,19 +5,23 @@ export const slackOutbound: ChannelOutboundAdapter = {
|
|||||||
deliveryMode: "direct",
|
deliveryMode: "direct",
|
||||||
chunker: null,
|
chunker: null,
|
||||||
textChunkLimit: 4000,
|
textChunkLimit: 4000,
|
||||||
sendText: async ({ to, text, accountId, deps, replyToId }) => {
|
sendText: async ({ to, text, accountId, deps, replyToId, threadId }) => {
|
||||||
const send = deps?.sendSlack ?? sendMessageSlack;
|
const send = deps?.sendSlack ?? sendMessageSlack;
|
||||||
|
// Use threadId fallback so routed tool notifications stay in the Slack thread.
|
||||||
|
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
|
||||||
const result = await send(to, text, {
|
const result = await send(to, text, {
|
||||||
threadTs: replyToId ?? undefined,
|
threadTs,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
});
|
});
|
||||||
return { channel: "slack", ...result };
|
return { channel: "slack", ...result };
|
||||||
},
|
},
|
||||||
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId }) => {
|
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId, threadId }) => {
|
||||||
const send = deps?.sendSlack ?? sendMessageSlack;
|
const send = deps?.sendSlack ?? sendMessageSlack;
|
||||||
|
// Use threadId fallback so routed tool notifications stay in the Slack thread.
|
||||||
|
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
|
||||||
const result = await send(to, text, {
|
const result = await send(to, text, {
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
threadTs: replyToId ?? undefined,
|
threadTs,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
});
|
});
|
||||||
return { channel: "slack", ...result };
|
return { channel: "slack", ...result };
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
runGatewayUpdate,
|
runGatewayUpdate,
|
||||||
type UpdateRunResult,
|
type UpdateRunResult,
|
||||||
type UpdateStepInfo,
|
type UpdateStepInfo,
|
||||||
|
type UpdateStepResult,
|
||||||
type UpdateStepProgress,
|
type UpdateStepProgress,
|
||||||
type UpdateStepResult,
|
type UpdateStepResult,
|
||||||
} from "../infra/update-runner.js";
|
} from "../infra/update-runner.js";
|
||||||
|
|||||||
@@ -476,10 +476,11 @@ export async function prepareSlackMessage(params: {
|
|||||||
Surface: "slack" as const,
|
Surface: "slack" as const,
|
||||||
MessageSid: message.ts,
|
MessageSid: message.ts,
|
||||||
ReplyToId: message.thread_ts ?? message.ts,
|
ReplyToId: message.thread_ts ?? message.ts,
|
||||||
|
// Preserve thread context for routed tool notifications (thread replies only).
|
||||||
|
MessageThreadId: isThreadReply ? threadTs : undefined,
|
||||||
ParentSessionKey: threadKeys.parentSessionKey,
|
ParentSessionKey: threadKeys.parentSessionKey,
|
||||||
ThreadStarterBody: threadStarterBody,
|
ThreadStarterBody: threadStarterBody,
|
||||||
ThreadLabel: threadLabel,
|
ThreadLabel: threadLabel,
|
||||||
MessageThreadId: isThreadReply ? threadTs : undefined,
|
|
||||||
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
Timestamp: message.ts ? Math.round(Number(message.ts) * 1000) : undefined,
|
||||||
WasMentioned: isRoomish ? effectiveWasMentioned : undefined,
|
WasMentioned: isRoomish ? effectiveWasMentioned : undefined,
|
||||||
MediaPath: media?.path,
|
MediaPath: media?.path,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
|
|
||||||
import SlackBoltDefault, * as SlackBoltModule from "@slack/bolt";
|
import SlackBolt from "@slack/bolt";
|
||||||
|
|
||||||
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
||||||
import { DEFAULT_GROUP_HISTORY_LIMIT } from "../../auto-reply/reply/history.js";
|
import { DEFAULT_GROUP_HISTORY_LIMIT } from "../../auto-reply/reply/history.js";
|
||||||
@@ -26,24 +26,14 @@ import { normalizeAllowList } from "./allow-list.js";
|
|||||||
|
|
||||||
import type { MonitorSlackOpts } from "./types.js";
|
import type { MonitorSlackOpts } from "./types.js";
|
||||||
|
|
||||||
type SlackBoltNamespace = typeof import("@slack/bolt");
|
const slackBoltModule = SlackBolt as typeof import("@slack/bolt") & {
|
||||||
type SlackBoltDefault = SlackBoltNamespace | SlackBoltNamespace["App"];
|
default?: typeof import("@slack/bolt");
|
||||||
|
};
|
||||||
const slackBoltDefaultImport = SlackBoltDefault as SlackBoltDefault | undefined;
|
// Bun allows named imports from CJS; Node ESM doesn't. Use default+fallback for compatibility.
|
||||||
const slackBoltModuleDefault = (SlackBoltModule as { default?: SlackBoltDefault }).default;
|
// Fix: Check if module has App property directly (Node 25.x ESM/CJS compat issue)
|
||||||
const slackBoltDefault = slackBoltDefaultImport ?? slackBoltModuleDefault;
|
const slackBolt =
|
||||||
const slackBoltNamespace =
|
(slackBoltModule.App ? slackBoltModule : slackBoltModule.default) ?? slackBoltModule;
|
||||||
typeof slackBoltDefault === "object" && slackBoltDefault
|
const { App, HTTPReceiver } = slackBolt;
|
||||||
? (slackBoltDefault as SlackBoltNamespace)
|
|
||||||
: typeof slackBoltModuleDefault === "object" && slackBoltModuleDefault
|
|
||||||
? (slackBoltModuleDefault as SlackBoltNamespace)
|
|
||||||
: undefined;
|
|
||||||
// Bun allows named imports from CJS; Node ESM doesn't. Resolve default/module shapes for compatibility.
|
|
||||||
const App = ((typeof slackBoltDefault === "function" ? slackBoltDefault : undefined) ??
|
|
||||||
slackBoltNamespace?.App ??
|
|
||||||
SlackBoltModule.App) as SlackBoltNamespace["App"];
|
|
||||||
const HTTPReceiver = (slackBoltNamespace?.HTTPReceiver ??
|
|
||||||
SlackBoltModule.HTTPReceiver) as SlackBoltNamespace["HTTPReceiver"];
|
|
||||||
function parseApiAppIdFromAppToken(raw?: string) {
|
function parseApiAppIdFromAppToken(raw?: string) {
|
||||||
const token = raw?.trim();
|
const token = raw?.trim();
|
||||||
if (!token) return undefined;
|
if (!token) return undefined;
|
||||||
@@ -133,13 +123,6 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
|
const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
|
||||||
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
||||||
|
|
||||||
if (!App) {
|
|
||||||
throw new Error("Slack Bolt App export missing; check @slack/bolt installation.");
|
|
||||||
}
|
|
||||||
if (slackMode === "http" && !HTTPReceiver) {
|
|
||||||
throw new Error("Slack Bolt HTTPReceiver export missing; check @slack/bolt installation.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const receiver =
|
const receiver =
|
||||||
slackMode === "http"
|
slackMode === "http"
|
||||||
? new HTTPReceiver({
|
? new HTTPReceiver({
|
||||||
|
|||||||
Reference in New Issue
Block a user