diff --git a/CHANGELOG.md b/CHANGELOG.md index 489f15890..e0cc4b88c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,70 +2,86 @@ Docs: https://docs.clawd.bot +## 2026.1.22 + +### Changes +- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster +- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.clawd.bot/tools/lobster +- Agents: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer. +- Memory: prevent CLI hangs by deferring vector probes, adding sqlite-vec/embedding timeouts, and showing sync progress early. +- Docs: add troubleshooting entry for gateway.mode blocking gateway start. https://docs.clawd.bot/gateway/troubleshooting +- Docs: add /model allowlist troubleshooting note. (#1405) +- Docs: add per-message Gmail search example for gog. (#1220) Thanks @mbelinky. +- UI: show per-session assistant identity in the Control UI. (#1420) Thanks @robbyczgw-cla. +- Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link. +- Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead). +- Signal: add typing indicators and DM read receipts via signal-cli. +- MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. +- CLI: add `clawdbot update wizard` for interactive channel selection and restart prompts. https://docs.clawd.bot/cli/update + +### Breaking +- **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert. + +### Fixes +- BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell. +- Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky. +- Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla. +- Control UI: resolve local avatar URLs with basePath across injection + identity RPC. (#1457) Thanks @dlauer. +- Agents: surface concrete API error details instead of generic AI service errors. +- Exec approvals: allow per-segment allowlists for chained shell commands on gateway + node hosts. (#1458) Thanks @czekaj. +- Agents: make OpenAI sessions image-sanitize-only; gate tool-id/repair sanitization by provider. +- Doctor: honor CLAWDBOT_GATEWAY_TOKEN for auth checks and security audit token reuse. (#1448) Thanks @azade-c. +- Agents: make tool summaries more readable and only show optional params when set. +- CLI: prefer `~` for home paths in output. +- Mattermost (plugin): enforce pairing/allowlist gating, keep @username targets, and clarify plugin-only docs. (#1428) Thanks @damoahdominic. +- Agents: centralize transcript sanitization in the runner; keep tags and error turns intact. +- Auth: skip auth profiles in cooldown during initial selection and rotation. (#1316) Thanks @odrobnik. +- Agents/TUI: honor user-pinned auth profiles during cooldown and preserve search picker ranking. (#1432) Thanks @tobiasbischoff. +- Docs: fix gog auth services example to include docs scope. (#1454) Thanks @zerone0x. +- Slack: read thread replies for message reads when threadId is provided (replies-only). (#1450) Thanks @rodrigouroz. +- macOS: prefer linked channels in gateway summary to avoid false “not linked” status. +- Providers: improve GitHub Copilot integration (enterprise support, base URL, and auth flow alignment). + ## 2026.1.21-2 ### Fixes - Control UI: ignore bootstrap identity placeholder text for avatar values and fall back to the default avatar. https://docs.clawd.bot/cli/agents https://docs.clawd.bot/web/control-ui +- Slack: remove deprecated `filetype` field from `files.uploadV2` to eliminate API warnings. (#1447) ## 2026.1.21 -### Highlights -- Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster -- Custom assistant identity + avatars in the Control UI. https://docs.clawd.bot/cli/agents https://docs.clawd.bot/web/control-ui -- Cache optimizations: cache-ttl pruning + defaults reduce token spend on cold requests. https://docs.clawd.bot/concepts/session-pruning -- Exec approvals + elevated ask/full modes. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/elevated -- Signal typing/read receipts + MSTeams attachments. https://docs.clawd.bot/channels/signal https://docs.clawd.bot/channels/msteams -- `/models` UX refresh + `clawdbot update wizard`. https://docs.clawd.bot/cli/models https://docs.clawd.bot/cli/update - ### Changes -- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster (#1152) Thanks @vignesh07. -- Agents/UI: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer. https://docs.clawd.bot/gateway/configuration https://docs.clawd.bot/cli/agents -- Control UI: add custom assistant identity support and per-session identity display. (#1420) Thanks @robbyczgw-cla. https://docs.clawd.bot/web/control-ui -- CLI: add `clawdbot update wizard` with interactive channel selection + restart prompts, plus preflight checks before rebasing. https://docs.clawd.bot/cli/update -- Models/Commands: add `/models`, improve `/model` listing UX, and expand `clawdbot models` paging. (#1398) Thanks @vignesh07. https://docs.clawd.bot/cli/models -- CLI: move gateway service commands under `clawdbot gateway`, flatten node service commands under `clawdbot node`, and add `gateway probe` for reachability. https://docs.clawd.bot/cli/gateway https://docs.clawd.bot/cli/node -- Exec: add elevated ask/full modes, tighten allowlist gating, and render approvals tables on write. https://docs.clawd.bot/tools/elevated https://docs.clawd.bot/tools/exec-approvals -- Exec approvals: default to local host, add gateway/node targeting + target details, support wildcard agent allowlists, and tighten allowlist parsing/safe bins. https://docs.clawd.bot/cli/approvals https://docs.clawd.bot/tools/exec-approvals -- Heartbeat: allow explicit session keys and active hours. (#1256) Thanks @zknicker. https://docs.clawd.bot/gateway/heartbeat -- Sessions: add per-channel idle durations via `sessions.channelIdleMinutes`. (#1353) Thanks @cash-echo-bot. -- Nodes: run exec-style, expose PATH in status/describe, and bootstrap PATH for node-host execution. https://docs.clawd.bot/cli/node -- Cache: add `cache.ttlPrune` mode and auth-aware defaults for cache TTL behavior. -- Queue: add per-channel debounce overrides for auto-reply. https://docs.clawd.bot/concepts/queue -- Discord: add wildcard channel config support. (#1334) Thanks @pvoo. https://docs.clawd.bot/channels/discord -- Signal: add typing indicators and DM read receipts via signal-cli. https://docs.clawd.bot/channels/signal -- MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. https://docs.clawd.bot/channels/msteams -- Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead). -- macOS: refresh Settings (location access in Permissions, connection mode in menu, remove CLI install UI). -- Diagnostics: add cache trace config for debugging. (#1370) Thanks @parubets. -- Docs: Lobster guides + org URL updates, /model allowlist troubleshooting, Gmail message search examples, gateway.mode troubleshooting, prompt injection guidance, npm prefix/node CLI notes, control UI dev gatewayUrl note, tool_use FAQ, showcase video, and sharp/node-gyp workaround. (#1427, #1220, #1405) Thanks @vignesh07, @mbelinky. +- Heartbeat: allow running heartbeats in an explicit session key. (#1256) Thanks @zknicker. +- CLI: default exec approvals to the local host, add gateway/node targeting flags, and show target details in allowlist output. +- CLI: exec approvals mutations render tables instead of raw JSON. +- Exec approvals: support wildcard agent allowlists (`*`) across all agents. +- Exec approvals: allowlist matches resolved binary paths only, add safe stdin-only bins, and tighten allowlist shell parsing. +- Nodes: expose node PATH in status/describe and bootstrap PATH for node-host execution. +- CLI: flatten node service commands under `clawdbot node` and remove `service node` docs. +- CLI: move gateway service commands under `clawdbot gateway` and add `gateway probe` for reachability. +- Sessions: add per-channel reset overrides via `session.resetByChannel`. (#1353) Thanks @cash-echo-bot. ### Breaking - **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.clawd.bot/web/control-ui#insecure-http -- **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert. ### Fixes -- Streaming/Typing/Media: keep reply tags across streamed chunks, start typing indicators at run start, and accept MEDIA paths with spaces/tilde while preferring the message tool hint for image replies. -- Agents/Providers: drop unsigned thinking blocks for Claude models (Google Antigravity) and enforce alphanumeric tool call ids for strict providers (Mistral/OpenRouter). (#1372) Thanks @zerone0x. -- Exec approvals: treat main as the default agent, align node/gateway allowlist prechecks, validate resolved paths, avoid allowlist resolve races, and avoid null optional params. (#1417, #1414, #1425) Thanks @czekaj. -- Exec/Windows: resolve Windows exec paths with extensions and handle safe-bin exe names. - Nodes/macOS: prompt on allowlist miss for node exec approvals, persist allowlist decisions, and flatten node invoke errors. (#1394) Thanks @ngutman. -- Gateway: prevent multiple gateways from sharing the same config/state (singleton lock), keep auto bind loopback-first with explicit tailnet binding, and improve SSH auth handling. (#1380) -- Control UI: remove the chat stop button, keep the composer aligned to the bottom edge, stabilize session previews, and refresh the debug panel on route-driven tab changes. (#1373) Thanks @yazinsai. -- UI/config: export `SECTION_META` for config form modules. (#1418) Thanks @MaudeBot. -- macOS: keep chat pinned during streaming replies, include Textual resources, respect wildcard exec approvals, allow SSH agent auth, and default distribution builds to universal binaries. (#1279, #1362, #1384, #1396) Thanks @ameno-, @JustYannicc. -- BlueBubbles: resolve short message IDs safely, expose full IDs in templates, and harden short-id fetch wrappers. (#1369, #1387) Thanks @tyler6204. -- Models/Configure: inherit session model overrides in threads/topics, map OpenCode Zen models to the correct APIs, narrow Anthropic OAuth allowlist handling, seed allowlist fallbacks, list the full catalog when no allowlist is set, and limit `/model` list output. (#1376, #1416) -- Memory: prevent CLI hangs by deferring vector probes, add sqlite-vec/embedding timeouts, and make session memory indexing async. -- Cron: cap reminder context history to 10 messages and honor `contextMessages`. (#1103) Thanks @mkbehr. -- Cache: restore the 1h cache TTL option and reset the pruning window. -- Zalo Personal: tolerate ANSI/log-prefixed JSON output from `zca`. (#1379) Thanks @ptn1411. -- Browser: suppress Chrome restore prompts for managed profiles. (#1419) Thanks @jamesgroat. -- Infra: preserve fetch helper methods/preconnect when wrapping abort signals and normalize Telegram fetch aborts. -- Config/Doctor: avoid stack traces for invalid configs, log the config path, avoid WhatsApp config resurrection, and warn when `gateway.mode` is unset. (#900) -- CLI: read Codex CLI account_id for workspace billing. (#1422) Thanks @aj47. -- Logs/Status: align rolling log filenames with local time and report sandboxed runtime in `clawdbot status`. (#1343) +- Gateway: keep auto bind loopback-first and add explicit tailnet binding to avoid Tailscale taking over local UI. (#1380) +- Agents: enforce 9-char alphanumeric tool call ids for Mistral providers. (#1372) Thanks @zerone0x. - Embedded runner: persist injected history images so attachments aren’t reloaded each turn. (#1374) Thanks @Nicell. -- Nodes/Subagents: include agent/node/gateway context in tool failure logs and ensure subagent list uses the command session. +- Nodes tool: include agent/node/gateway context in tool failure logs to speed approval debugging. +- macOS: exec approvals now respect wildcard agent allowlists (`*`). +- macOS: allow SSH agent auth when no identity file is set. (#1384) Thanks @ameno-. +- Gateway: prevent multiple gateways from sharing the same config/state at once (singleton lock). +- UI: remove the chat stop button and keep the composer aligned to the bottom edge. +- Typing: start instant typing indicators at run start so DMs and mentions show immediately. +- Configure: restrict the model allowlist picker to OAuth-compatible Anthropic models and preselect Opus 4.5. +- Configure: seed model fallbacks from the allowlist selection when multiple models are chosen. +- Model picker: list the full catalog when no model allowlist is configured. +- Discord: honor wildcard channel configs via shared match helpers. (#1334) Thanks @pvoo. +- BlueBubbles: resolve short message IDs safely and expose full IDs in templates. (#1387) Thanks @tyler6204. +- Infra: preserve fetch helper methods when wrapping abort signals. (#1387) +- macOS: default distribution packaging to universal binaries. (#1396) Thanks @JustYannicc. ## 2026.1.20 diff --git a/README.md b/README.md index 2a58b9155..a08760ae6 100644 --- a/README.md +++ b/README.md @@ -477,28 +477,29 @@ Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and Thanks to all clawtributors:

- steipete bohdanpodvirnyi joaohlisboa mneves75 MatthieuBizien MaudeBot rahthakor vrknetha radek-paclt joshp123 - mukhtharcm maxsumrall xadenryan Tobias Bischoff juanpablodlc hsrvc magimetal meaningfool NicholasSpisak sebslight + steipete bohdanpodvirnyi joaohlisboa mneves75 MatthieuBizien MaudeBot rahthakor vrknetha radek-paclt Tobias Bischoff + joshp123 mukhtharcm maxsumrall xadenryan juanpablodlc hsrvc magimetal meaningfool NicholasSpisak sebslight abhisekbasu1 jamesgroat zerone0x claude SocialNerd42069 Hyaxia dantelex daveonkels mteam88 Eng. Juan Combetto Mariano Belinky dbhurley TSavo julianengel benithors bradleypriest timolins nachx639 sreekaransrinath gupsammy cristip73 nachoiacovino Vasanth Rao Naik Sabavat cpojer lc0rp scald gumadeiras andranik-sahakyan davidguttman sleontenko sircrumpet peschee rafaelreis-r thewilloftheshadow ratulsarna lutr0 danielz1z emanuelst KristijanJovanovski CashWilliams rdev osolmaz joshrad-dev kiranjd adityashaw2 sheeek artuskg onutc tyler6204 manuelhettich - minghinmatthewlam myfunc buddyh connorshea vignesh07 mcinteerj dependabot[bot] John-Rood timkrase gerardward2007 + minghinmatthewlam myfunc vignesh07 buddyh connorshea mcinteerj dependabot[bot] John-Rood timkrase gerardward2007 obviyus tosh-hamburg azade-c roshanasingh4 bjesuiter cheeeee Josh Phillips Whoaa512 YuriNachos chriseidhof - ysqander superman32432432 Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] dan-dr dlauer HeimdallStrategy - imfing jalehman jarvis-medmatic kkarimi mahmoudashraf93 ngutman petter-b pkrmf RandyVentures Ryan Lisse - dougvk erikpr1994 Ghost jonasjancarik Keith the Silly Goose L36 Server Marc mitschabaude-bot mkbehr neist - chrisrodz Friederike Seiler gabriel-trigo iamadig Kit koala73 manmal ogulcancelik pasogott petradonka - rubyrunsstuff sibbl siddhantjain suminhthanh VACInc wes-davis zats 24601 Chris Taylor czekaj - Django Navarro evalexpr henrino3 humanwritten larlyssa oswalpalash pcty-nextgen-service-account Syhids Aaron Konyer aaronveklabs - adam91holt ameno- cash-echo-bot Clawd ClawdFx erik-agens fcatuhe ivanrvpereira jayhickey jeffersonwarrior - jeffersonwarrior Jonathan D. Rhyne (DJ-D) jverdi longmaba mickahouan mjrussell p6l-richard philipp-spiess robaxelsen Sash Catanzarite - T5-AndyML VAC zknicker aj47 alejandro maza andrewting19 Andrii anpoirier Asleep123 bolismauro - conhecendoia Dimitrios Ploutarchos Drake Thomsen Felix Krause gtsifrikas HazAT hrdwdmrbl hugobarauna Jamie Openshaw Jarvis - Jefferson Nunn Kevin Lin kitze levifig Lloyd loukotal martinpucik Miles mrdbstn MSch - Mustafa Tag Eldeen ndraiman nexty5870 odysseus0 prathamdby ptn1411 reeltimeapps RLTCmpe robbyczgw-cla rodrigouroz - Rolf Fredheim Rony Kelner Samrat Jha siraht snopoke The Admiral thesash Ubuntu voidserf Vultr-Clawd Admin - Wimmie wstock yazinsai Zach Knickerbocker Alphonse-arianee Azade carlulsoe ddyo Erik latitudeki5223 - Manuel Maly Mourad Boustani odrobnik pcty-nextgen-ios-builder Quentin Randy Torres rhjoh ronak-guliani William Stock + ysqander dlauer superman32432432 Yurii Chukhlib grp06 antons austinm911 blacksmith-sh[bot] damoahdominic dan-dr + HeimdallStrategy imfing jalehman jarvis-medmatic kkarimi mahmoudashraf93 ngutman petter-b pkrmf RandyVentures + robbyczgw-cla Ryan Lisse dougvk erikpr1994 Ghost jonasjancarik Keith the Silly Goose L36 Server Marc mitschabaude-bot + mkbehr neist chrisrodz czekaj Friederike Seiler gabriel-trigo iamadig Jonathan D. Rhyne (DJ-D) Kit koala73 + manmal ogulcancelik pasogott petradonka rubyrunsstuff sibbl siddhantjain suminhthanh VACInc wes-davis + zats 24601 ameno- Chris Taylor Django Navarro evalexpr henrino3 humanwritten larlyssa oswalpalash + pcty-nextgen-service-account Syhids Aaron Konyer aaronveklabs adam91holt cash-echo-bot Clawd ClawdFx erik-agens fcatuhe + ivanrvpereira jayhickey jeffersonwarrior jeffersonwarrior jverdi longmaba mickahouan mjrussell p6l-richard philipp-spiess + robaxelsen Sash Catanzarite T5-AndyML VAC zknicker aj47 alejandro maza andrewting19 Andrii anpoirier + Asleep123 bolismauro conhecendoia Dimitrios Ploutarchos Drake Thomsen Felix Krause gtsifrikas HazAT hrdwdmrbl hugobarauna + Jamie Openshaw Jarvis Jefferson Nunn Kevin Lin kitze levifig Lloyd loukotal martinpucik Matt mini + Miles mrdbstn MSch Mustafa Tag Eldeen ndraiman nexty5870 odysseus0 prathamdby ptn1411 reeltimeapps + RLTCmpe rodrigouroz Rolf Fredheim Rony Kelner Samrat Jha siraht snopoke testingabc321 The Admiral thesash + Ubuntu voidserf Vultr-Clawd Admin Wimmie wstock yazinsai Zach Knickerbocker Alphonse-arianee Azade carlulsoe + ddyo Erik latitudeki5223 Manuel Maly Mourad Boustani odrobnik pauloportella pcty-nextgen-ios-builder Quentin Randy Torres + rhjoh ronak-guliani William Stock

diff --git a/apps/macos/Sources/Clawdbot/DebugSettings.swift b/apps/macos/Sources/Clawdbot/DebugSettings.swift index 542c69112..51886bc5d 100644 --- a/apps/macos/Sources/Clawdbot/DebugSettings.swift +++ b/apps/macos/Sources/Clawdbot/DebugSettings.swift @@ -16,6 +16,8 @@ struct DebugSettings: View { @State private var modelsError: String? private let gatewayManager = GatewayProcessManager.shared private let healthStore = HealthStore.shared + @State private var launchAgentWriteDisabled = GatewayLaunchAgentManager.isLaunchAgentWriteDisabled() + @State private var launchAgentWriteError: String? @State private var gatewayRootInput: String = GatewayProcessManager.shared.projectRootPath() @State private var sessionStorePath: String = SessionLoader.defaultStorePath @State private var sessionStoreSaveError: String? @@ -47,6 +49,7 @@ struct DebugSettings: View { VStack(alignment: .leading, spacing: 14) { self.header + self.launchdSection self.appInfoSection self.gatewaySection self.logsSection @@ -79,6 +82,39 @@ struct DebugSettings: View { } } + private var launchdSection: some View { + GroupBox("Gateway startup") { + VStack(alignment: .leading, spacing: 8) { + Toggle("Attach only (skip launchd install)", isOn: self.$launchAgentWriteDisabled) + .onChange(of: self.launchAgentWriteDisabled) { _, newValue in + self.launchAgentWriteError = GatewayLaunchAgentManager.setLaunchAgentWriteDisabled(newValue) + if self.launchAgentWriteError != nil { + self.launchAgentWriteDisabled = GatewayLaunchAgentManager.isLaunchAgentWriteDisabled() + return + } + if newValue { + Task { + _ = await GatewayLaunchAgentManager.set( + enabled: false, + bundlePath: Bundle.main.bundlePath, + port: GatewayEnvironment.gatewayPort()) + } + } + } + + Text("When enabled, Clawdbot won't install or manage \(gatewayLaunchdLabel). It will only attach to an existing Gateway.") + .font(.caption) + .foregroundStyle(.secondary) + + if let launchAgentWriteError { + Text(launchAgentWriteError) + .font(.caption) + .foregroundStyle(.red) + } + } + } + } + private var header: some View { VStack(alignment: .leading, spacing: 6) { Text("Debug") diff --git a/apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift b/apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift index 154932c64..f0896e691 100644 --- a/apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift +++ b/apps/macos/Sources/Clawdbot/GatewayLaunchAgentManager.swift @@ -4,11 +4,46 @@ enum GatewayLaunchAgentManager { private static let logger = Logger(subsystem: "com.clawdbot", category: "gateway.launchd") private static let disableLaunchAgentMarker = ".clawdbot/disable-launchagent" + private static var disableLaunchAgentMarkerURL: URL { + FileManager().homeDirectoryForCurrentUser + .appendingPathComponent(self.disableLaunchAgentMarker) + } + private static var plistURL: URL { FileManager().homeDirectoryForCurrentUser .appendingPathComponent("Library/LaunchAgents/\(gatewayLaunchdLabel).plist") } + static func isLaunchAgentWriteDisabled() -> Bool { + FileManager().fileExists(atPath: self.disableLaunchAgentMarkerURL.path) + } + + static func setLaunchAgentWriteDisabled(_ disabled: Bool) -> String? { + let marker = self.disableLaunchAgentMarkerURL + if disabled { + do { + try FileManager().createDirectory( + at: marker.deletingLastPathComponent(), + withIntermediateDirectories: true) + if !FileManager().fileExists(atPath: marker.path) { + FileManager().createFile(atPath: marker.path, contents: nil) + } + } catch { + return error.localizedDescription + } + return nil + } + + if FileManager().fileExists(atPath: marker.path) { + do { + try FileManager().removeItem(at: marker) + } catch { + return error.localizedDescription + } + } + return nil + } + static func isLoaded() async -> Bool { guard let loaded = await self.readDaemonLoaded() else { return false } return loaded @@ -66,12 +101,6 @@ enum GatewayLaunchAgentManager { } extension GatewayLaunchAgentManager { - private static func isLaunchAgentWriteDisabled() -> Bool { - let marker = FileManager().homeDirectoryForCurrentUser - .appendingPathComponent(self.disableLaunchAgentMarker) - return FileManager().fileExists(atPath: marker.path) - } - private static func readDaemonLoaded() async -> Bool? { let result = await self.runDaemonCommandResult( ["status", "--json", "--no-probe"], diff --git a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift b/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift index 33156f58f..4dc02593b 100644 --- a/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift +++ b/apps/macos/Sources/Clawdbot/GatewayProcessManager.swift @@ -79,6 +79,11 @@ final class GatewayProcessManager { func ensureLaunchAgentEnabledIfNeeded() async { guard !CommandResolver.connectionModeIsRemote() else { return } + if GatewayLaunchAgentManager.isLaunchAgentWriteDisabled() { + self.appendLog("[gateway] launchd auto-enable skipped (attach-only)\n") + self.logger.info("gateway launchd auto-enable skipped (disable marker set)") + return + } let enabled = await GatewayLaunchAgentManager.isLoaded() guard !enabled else { return } let bundlePath = Bundle.main.bundleURL.path @@ -237,13 +242,12 @@ final class GatewayProcessManager { private func describe(details instance: String?, port: Int, snap: HealthSnapshot?) -> String { let instanceText = instance ?? "pid unknown" if let snap { - let linkId = snap.channelOrder?.first(where: { - if let summary = snap.channels[$0] { return summary.linked != nil } - return false - }) ?? snap.channels.keys.first(where: { - if let summary = snap.channels[$0] { return summary.linked != nil } - return false - }) + let order = snap.channelOrder ?? Array(snap.channels.keys) + let linkId = order.first(where: { snap.channels[$0]?.linked == true }) + ?? order.first(where: { snap.channels[$0]?.linked != nil }) + guard let linkId else { + return "port \(port), health probe succeeded, \(instanceText)" + } let linked = linkId.flatMap { snap.channels[$0]?.linked } ?? false let authAge = linkId.flatMap { snap.channels[$0]?.authAgeMs }.flatMap(msToAge) ?? "unknown age" let label = @@ -308,6 +312,15 @@ final class GatewayProcessManager { return } + if GatewayLaunchAgentManager.isLaunchAgentWriteDisabled() { + let message = "Launchd disabled; start the Gateway manually or disable attach-only." + self.status = .failed(message) + self.lastFailureReason = "launchd disabled" + self.appendLog("[gateway] launchd disabled; skipping auto-start\n") + self.logger.info("gateway launchd enable skipped (disable marker set)") + return + } + let bundlePath = Bundle.main.bundleURL.path let port = GatewayEnvironment.gatewayPort() self.appendLog("[gateway] enabling launchd job (\(gatewayLaunchdLabel)) on port \(port)\n") diff --git a/apps/macos/Sources/Clawdbot/HealthStore.swift b/apps/macos/Sources/Clawdbot/HealthStore.swift index cc3d5e136..44ebcc053 100644 --- a/apps/macos/Sources/Clawdbot/HealthStore.swift +++ b/apps/macos/Sources/Clawdbot/HealthStore.swift @@ -166,6 +166,11 @@ final class HealthStore { _ snap: HealthSnapshot) -> (id: String, summary: HealthSnapshot.ChannelSummary)? { let order = snap.channelOrder ?? Array(snap.channels.keys) + for id in order { + if let summary = snap.channels[id], summary.linked == true { + return (id: id, summary: summary) + } + } for id in order { if let summary = snap.channels[id], summary.linked != nil { return (id: id, summary: summary) diff --git a/apps/macos/Sources/Clawdbot/MenuBar.swift b/apps/macos/Sources/Clawdbot/MenuBar.swift index 9738c310b..26a467e91 100644 --- a/apps/macos/Sources/Clawdbot/MenuBar.swift +++ b/apps/macos/Sources/Clawdbot/MenuBar.swift @@ -3,6 +3,7 @@ import Darwin import Foundation import MenuBarExtraAccess import Observation +import OSLog import Security import SwiftUI @@ -10,6 +11,7 @@ import SwiftUI struct ClawdbotApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) private var delegate @State private var state: AppState + private static let logger = Logger(subsystem: "com.clawdbot", category: "app") private let gatewayManager = GatewayProcessManager.shared private let controlChannel = ControlChannel.shared private let activityStore = WorkActivityStore.shared @@ -31,6 +33,7 @@ struct ClawdbotApp: App { init() { ClawdbotLogging.bootstrapIfNeeded() + Self.applyAttachOnlyOverrideIfNeeded() _state = State(initialValue: AppStateStore.shared) } @@ -91,6 +94,22 @@ struct ClawdbotApp: App { self.statusItem?.button?.appearsDisabled = paused || sleeping } + private static func applyAttachOnlyOverrideIfNeeded() { + let args = CommandLine.arguments + guard args.contains("--attach-only") || args.contains("--no-launchd") else { return } + if let error = GatewayLaunchAgentManager.setLaunchAgentWriteDisabled(true) { + Self.logger.error("attach-only flag failed: \(error, privacy: .public)") + return + } + Task { + _ = await GatewayLaunchAgentManager.set( + enabled: false, + bundlePath: Bundle.main.bundlePath, + port: GatewayEnvironment.gatewayPort()) + } + Self.logger.info("attach-only flag enabled") + } + private var isGatewaySleeping: Bool { if self.state.isPaused { return false } switch self.state.connectionMode { diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md index 882cf816a..33fec7219 100644 --- a/docs/automation/cron-jobs.md +++ b/docs/automation/cron-jobs.md @@ -121,7 +121,7 @@ Resolution priority: ### Delivery (channel + target) Isolated jobs can deliver output to a channel. The job payload can specify: -- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage` / `last` +- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last` - `to`: channel-specific recipient target If `channel` or `to` is omitted, cron can fall back to the main session’s “last route” @@ -133,7 +133,7 @@ Delivery notes: - Use `deliver: false` to keep output internal even if a `to` is present. Target format reminders: -- Slack/Discord targets should use explicit prefixes (e.g. `channel:`, `user:`) to avoid ambiguity. +- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:`, `user:`) to avoid ambiguity. - Telegram topics should use the `:topic:` form (see below). #### Telegram delivery targets (topics / forum threads) diff --git a/docs/automation/webhook.md b/docs/automation/webhook.md index f2a62b4e3..0828483d2 100644 --- a/docs/automation/webhook.md +++ b/docs/automation/webhook.md @@ -71,8 +71,8 @@ Payload: - `sessionKey` optional (string): The key used to identify the agent's session. Defaults to a random `hook:`. Using a consistent key allows for a multi-turn conversation within the hook context. - `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check. - `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped. -- `channel` optional (string): The messaging channel for delivery. One of: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `msteams`. Defaults to `last`. -- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack, conversation ID for MS Teams). Defaults to the last recipient in the main session. +- `channel` optional (string): The messaging channel for delivery. One of: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `mattermost` (plugin), `signal`, `imessage`, `msteams`. Defaults to `last`. +- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for MS Teams). Defaults to the last recipient in the main session. - `model` optional (string): Model override (e.g., `anthropic/claude-3-5-sonnet` or an alias). Must be in the allowed model list if restricted. - `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`). - `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds. diff --git a/docs/channels/index.md b/docs/channels/index.md index af1d5bfee..00b33ac07 100644 --- a/docs/channels/index.md +++ b/docs/channels/index.md @@ -15,6 +15,7 @@ Text is supported everywhere; media and reactions vary by channel. - [Telegram](/channels/telegram) — Bot API via grammY; supports groups. - [Discord](/channels/discord) — Discord Bot API + Gateway; supports servers, channels, and DMs. - [Slack](/channels/slack) — Bolt SDK; workspace apps. +- [Mattermost](/channels/mattermost) — Bot API + WebSocket; channels, groups, DMs (plugin, installed separately). - [Signal](/channels/signal) — signal-cli; privacy-focused. - [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe). - [iMessage](/channels/imessage) — macOS only; native integration via imsg (legacy, consider BlueBubbles for new setups). diff --git a/docs/channels/mattermost.md b/docs/channels/mattermost.md new file mode 100644 index 000000000..de4771745 --- /dev/null +++ b/docs/channels/mattermost.md @@ -0,0 +1,123 @@ +--- +summary: "Mattermost bot setup and Clawdbot config" +read_when: + - Setting up Mattermost + - Debugging Mattermost routing +--- + +# Mattermost (plugin) + +Status: supported via plugin (bot token + WebSocket events). Channels, groups, and DMs are supported. +Mattermost is a self-hostable team messaging platform; see the official site at +[mattermost.com](https://mattermost.com) for product details and downloads. + +## Plugin required +Mattermost ships as a plugin and is not bundled with the core install. + +Install via CLI (npm registry): +```bash +clawdbot plugins install @clawdbot/mattermost +``` + +Local checkout (when running from a git repo): +```bash +clawdbot plugins install ./extensions/mattermost +``` + +If you choose Mattermost during configure/onboarding and a git checkout is detected, +Clawdbot will offer the local install path automatically. + +Details: [Plugins](/plugin) + +## Quick setup +1) Install the Mattermost plugin. +2) Create a Mattermost bot account and copy the **bot token**. +3) Copy the Mattermost **base URL** (e.g., `https://chat.example.com`). +4) Configure Clawdbot and start the gateway. + +Minimal config: +```json5 +{ + channels: { + mattermost: { + enabled: true, + botToken: "mm-token", + baseUrl: "https://chat.example.com", + dmPolicy: "pairing" + } + } +} +``` + +## Environment variables (default account) +Set these on the gateway host if you prefer env vars: + +- `MATTERMOST_BOT_TOKEN=...` +- `MATTERMOST_URL=https://chat.example.com` + +Env vars apply only to the **default** account (`default`). Other accounts must use config values. + +## Chat modes +Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`: + +- `oncall` (default): respond only when @mentioned in channels. +- `onmessage`: respond to every channel message. +- `onchar`: respond when a message starts with a trigger prefix. + +Config example: +```json5 +{ + channels: { + mattermost: { + chatmode: "onchar", + oncharPrefixes: [">", "!"] + } + } +} +``` + +Notes: +- `onchar` still responds to explicit @mentions. +- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred. + +## Access control (DMs) +- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code). +- Approve via: + - `clawdbot pairing list mattermost` + - `clawdbot pairing approve mattermost ` +- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`. + +## Channels (groups) +- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated). +- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`). +- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated). + +## Targets for outbound delivery +Use these target formats with `clawdbot message send` or cron/webhooks: + +- `channel:` for a channel +- `user:` for a DM +- `@username` for a DM (resolved via the Mattermost API) + +Bare IDs are treated as channels. + +## Multi-account +Mattermost supports multiple accounts under `channels.mattermost.accounts`: + +```json5 +{ + channels: { + mattermost: { + accounts: { + default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" }, + alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" } + } + } + } +} +``` + +## Troubleshooting +- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`. +- Auth errors: check the bot token, base URL, and whether the account is enabled. +- Multi-account issues: env vars only apply to the `default` account. diff --git a/docs/cli/channels.md b/docs/cli/channels.md index 55214ae63..48ed043a2 100644 --- a/docs/cli/channels.md +++ b/docs/cli/channels.md @@ -1,7 +1,7 @@ --- summary: "CLI reference for `clawdbot channels` (accounts, status, login/logout, logs)" read_when: - - You want to add/remove channel accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage) + - You want to add/remove channel accounts (WhatsApp/Telegram/Discord/Slack/Mattermost (plugin)/Signal/iMessage) - You want to check channel status or tail channel logs --- diff --git a/docs/cli/gateway.md b/docs/cli/gateway.md index 253130780..017718745 100644 --- a/docs/cli/gateway.md +++ b/docs/cli/gateway.md @@ -122,7 +122,7 @@ clawdbot gateway probe --ssh user@gateway-host Options: - `--ssh `: `user@host` or `user@host:port` (port defaults to `22`). - `--ssh-identity `: identity file. -- `--ssh-auto`: pick the first discovered bridge host as SSH target (LAN/WAB only). +- `--ssh-auto`: pick the first discovered gateway host as SSH target (LAN/WAB only). Config (optional, used as defaults): - `gateway.remote.sshTarget` diff --git a/docs/cli/index.md b/docs/cli/index.md index 1166d7508..46f6d173e 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -293,7 +293,7 @@ Options: - `--reset` (reset config + credentials + sessions + workspace before wizard) - `--non-interactive` - `--mode ` -- `--flow ` +- `--flow ` (manual is an alias for advanced) - `--auth-choice ` - `--token-provider ` (non-interactive; used with `--auth-choice token`) - `--token ` (non-interactive; used with `--auth-choice token`) @@ -352,7 +352,7 @@ Options: ## Channel helpers ### `channels` -Manage chat channel accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage/MS Teams). +Manage chat channel accounts (WhatsApp/Telegram/Discord/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams). Subcommands: - `channels list`: show configured channels and auth profiles (Claude Code + Codex CLI OAuth sync included). @@ -365,7 +365,7 @@ Subcommands: - `channels logout`: log out of a channel session (if supported). Common options: -- `--channel `: `whatsapp|telegram|discord|slack|signal|imessage|msteams` +- `--channel `: `whatsapp|telegram|discord|slack|mattermost|signal|imessage|msteams` - `--account `: channel account id (default `default`) - `--name