From 5cac35234cd5e3239235f5f11c5e1b3955f45203 Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 20 Jan 2026 21:57:28 -0500 Subject: [PATCH 01/14] Update gateway version to 2026.1.16-2 - Update rev to be37b39782e0799ba5b9533561de6d128d50c863 - Update source hash - Update pnpmDepsHash Co-Authored-By: Claude Opus 4.5 --- nix/packages/clawdbot-gateway.nix | 2 +- nix/sources/clawdbot-source.nix | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/packages/clawdbot-gateway.nix b/nix/packages/clawdbot-gateway.nix index 11c17b01..99c18779 100644 --- a/nix/packages/clawdbot-gateway.nix +++ b/nix/packages/clawdbot-gateway.nix @@ -38,7 +38,7 @@ in stdenv.mkDerivation (finalAttrs: { pname = "clawdbot-gateway"; - version = "2026.1.8-2"; + version = "2026.1.16-2"; src = if gatewaySrc != null then gatewaySrc else fetchFromGitHub sourceFetch; diff --git a/nix/sources/clawdbot-source.nix b/nix/sources/clawdbot-source.nix index 1dab4cbc..afc59e90 100644 --- a/nix/sources/clawdbot-source.nix +++ b/nix/sources/clawdbot-source.nix @@ -2,7 +2,7 @@ { owner = "clawdbot"; repo = "clawdbot"; - rev = "e81ca7ab00ae8bf325691154cb3d8e2f410936ea"; - hash = "sha256-HQRCRuCCWUwXN/KJ2S+8UzmxSkfRVPlGl14MK2fgvoc="; - pnpmDepsHash = "sha256-Uo++PJBXKk7pN6OwezMnAYQXR77lCYJkru7J+t/KK5U="; + rev = "be37b39782e0799ba5b9533561de6d128d50c863"; + hash = "sha256-y1ToqEcfl0yVAJkVld0k5AX5tztiE7yJt/F7Rhg+dAc="; + pnpmDepsHash = "sha256-NPQrkhhvAoIYzR1gopqsErps1K/HkfxmrPXpyMlN0Bc="; } From eb97fa500a14233074d94a8ef6962fde2a5969ab Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 20 Jan 2026 21:59:32 -0500 Subject: [PATCH 02/14] Fix defaultInstance missing launchd.label, systemd.unitName, and agent The defaultInstance was missing required attributes that are expected by the instance module: - launchd.label (was only inheriting enable from cfg.launchd) - systemd.unitName (was only inheriting enable from cfg.systemd) - agent (model and thinkingDefault) This caused errors when using the simple `programs.clawdbot.enable = true` configuration without explicit instances. Co-Authored-By: Claude Opus 4.5 --- nix/modules/home-manager/clawdbot.nix | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/nix/modules/home-manager/clawdbot.nix b/nix/modules/home-manager/clawdbot.nix index 1b59d8b3..717ebc1f 100644 --- a/nix/modules/home-manager/clawdbot.nix +++ b/nix/modules/home-manager/clawdbot.nix @@ -302,9 +302,19 @@ let gatewayPort = 18789; providers = cfg.providers; routing = cfg.routing; - launchd = cfg.launchd; - systemd = cfg.systemd; + launchd = { + enable = cfg.launchd.enable; + label = "com.steipete.clawdbot.gateway"; + }; + systemd = { + enable = cfg.systemd.enable; + unitName = "clawdbot-gateway"; + }; plugins = cfg.plugins; + agent = { + model = cfg.defaults.model; + thinkingDefault = cfg.defaults.thinkingDefault; + }; configOverrides = {}; config = {}; appDefaults = { From 437fc4e26237285a051d2e9fedcf5018cdfc90d5 Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 20 Jan 2026 22:00:15 -0500 Subject: [PATCH 03/14] Add missing gatewayPath and gatewayPnpmDepsHash to defaultInstance These attributes are required by mkInstanceConfig but were missing from the defaultInstance definition, causing build errors when using the simple configuration without explicit instances. Co-Authored-By: Claude Opus 4.5 --- nix/modules/home-manager/clawdbot.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/modules/home-manager/clawdbot.nix b/nix/modules/home-manager/clawdbot.nix index 717ebc1f..793853c7 100644 --- a/nix/modules/home-manager/clawdbot.nix +++ b/nix/modules/home-manager/clawdbot.nix @@ -300,6 +300,8 @@ let configPath = "${cfg.stateDir}/clawdbot.json"; logPath = "/tmp/clawdbot/clawdbot-gateway.log"; gatewayPort = 18789; + gatewayPath = null; + gatewayPnpmDepsHash = lib.fakeHash; providers = cfg.providers; routing = cfg.routing; launchd = { From 97e4c91d1cc49133555aa2045b78be731206efba Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 20 Jan 2026 22:00:42 -0500 Subject: [PATCH 04/14] Add missing groups attribute to telegram provider in defaultInstance The top-level providers.telegram doesn't include groups, but the instance module expects it. Add default empty groups. Co-Authored-By: Claude Opus 4.5 --- nix/modules/home-manager/clawdbot.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nix/modules/home-manager/clawdbot.nix b/nix/modules/home-manager/clawdbot.nix index 793853c7..ceb36f47 100644 --- a/nix/modules/home-manager/clawdbot.nix +++ b/nix/modules/home-manager/clawdbot.nix @@ -302,7 +302,12 @@ let gatewayPort = 18789; gatewayPath = null; gatewayPnpmDepsHash = lib.fakeHash; - providers = cfg.providers; + providers = { + anthropic = cfg.providers.anthropic; + telegram = cfg.providers.telegram // { + groups = {}; + }; + }; routing = cfg.routing; launchd = { enable = cfg.launchd.enable; From 3135c10ff4f4a6d1c77e2a9b729144144c976f0c Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 20 Jan 2026 22:11:05 -0500 Subject: [PATCH 05/14] Update version in check derivations to 2026.1.16-2 Missed updating the version in clawdbot-gateway-tests.nix and clawdbot-config-options.nix check derivations. Co-Authored-By: Claude Opus 4.5 --- nix/checks/clawdbot-config-options.nix | 2 +- nix/checks/clawdbot-gateway-tests.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/checks/clawdbot-config-options.nix b/nix/checks/clawdbot-config-options.nix index 0a54cb3a..c40b8459 100644 --- a/nix/checks/clawdbot-config-options.nix +++ b/nix/checks/clawdbot-config-options.nix @@ -34,7 +34,7 @@ in stdenv.mkDerivation (finalAttrs: { pname = "clawdbot-config-options"; - version = "2026.1.8-2"; + version = "2026.1.16-2"; src = fetchFromGitHub sourceFetch; diff --git a/nix/checks/clawdbot-gateway-tests.nix b/nix/checks/clawdbot-gateway-tests.nix index 2deffd4e..d3ddad30 100644 --- a/nix/checks/clawdbot-gateway-tests.nix +++ b/nix/checks/clawdbot-gateway-tests.nix @@ -35,7 +35,7 @@ in stdenv.mkDerivation (finalAttrs: { pname = "clawdbot-gateway-tests"; - version = "2026.1.8-2"; + version = "2026.1.16-2"; src = fetchFromGitHub sourceFetch; From 7506f8d65ff72986378a72a64d393575f7f5c98a Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 20 Jan 2026 22:16:51 -0500 Subject: [PATCH 06/14] Regenerate clawdbot-config-options.nix for v2026.1.16-2 The config schema changed in v2026.1.16-2: - Removed perplexity from web search providers - Removed dev channel from update options - Various other schema changes Generated from clawdbot source using generate-config-options.ts Co-Authored-By: Claude Opus 4.5 --- nix/generated/clawdbot-config-options.nix | 793 +++------------------- 1 file changed, 87 insertions(+), 706 deletions(-) diff --git a/nix/generated/clawdbot-config-options.nix b/nix/generated/clawdbot-config-options.nix index 5041202e..dd19fd43 100644 --- a/nix/generated/clawdbot-config-options.nix +++ b/nix/generated/clawdbot-config-options.nix @@ -194,15 +194,6 @@ in elevatedDefault = lib.mkOption { type = t.oneOf [ t.enum [ "off" ] t.enum [ "on" ] ]; }; - envelopeElapsed = lib.mkOption { - type = t.oneOf [ t.enum [ "on" ] t.enum [ "off" ] ]; - }; - envelopeTimestamp = lib.mkOption { - type = t.oneOf [ t.enum [ "on" ] t.enum [ "off" ] ]; - }; - envelopeTimezone = lib.mkOption { - type = t.str; - }; heartbeat = lib.mkOption { type = t.submodule { options = { ackMaxChars = lib.mkOption { @@ -259,16 +250,6 @@ in }; memorySearch = lib.mkOption { type = t.submodule { options = { - cache = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - maxEntries = lib.mkOption { - type = t.int; - }; - }; }; - }; chunking = lib.mkOption { type = t.submodule { options = { overlap = lib.mkOption { @@ -282,15 +263,8 @@ in enabled = lib.mkOption { type = t.bool; }; - experimental = lib.mkOption { - type = t.submodule { options = { - sessionMemory = lib.mkOption { - type = t.bool; - }; - }; }; - }; fallback = lib.mkOption { - type = t.oneOf [ t.enum [ "openai" ] t.enum [ "gemini" ] t.enum [ "local" ] t.enum [ "none" ] ]; + type = t.oneOf [ t.enum [ "openai" ] t.enum [ "none" ] ]; }; local = lib.mkOption { type = t.submodule { options = { @@ -306,26 +280,10 @@ in type = t.str; }; provider = lib.mkOption { - type = t.oneOf [ t.enum [ "openai" ] t.enum [ "local" ] t.enum [ "gemini" ] ]; + type = t.oneOf [ t.enum [ "openai" ] t.enum [ "local" ] ]; }; query = lib.mkOption { type = t.submodule { options = { - hybrid = lib.mkOption { - type = t.submodule { options = { - candidateMultiplier = lib.mkOption { - type = t.int; - }; - enabled = lib.mkOption { - type = t.bool; - }; - textWeight = lib.mkOption { - type = t.number; - }; - vectorWeight = lib.mkOption { - type = t.number; - }; - }; }; - }; maxResults = lib.mkOption { type = t.int; }; @@ -342,33 +300,11 @@ in baseUrl = lib.mkOption { type = t.str; }; - batch = lib.mkOption { - type = t.submodule { options = { - concurrency = lib.mkOption { - type = t.int; - }; - enabled = lib.mkOption { - type = t.bool; - }; - pollIntervalMs = lib.mkOption { - type = t.int; - }; - timeoutMinutes = lib.mkOption { - type = t.int; - }; - wait = lib.mkOption { - type = t.bool; - }; - }; }; - }; headers = lib.mkOption { type = t.attrsOf (t.str); }; }; }; }; - sources = lib.mkOption { - type = t.listOf (t.oneOf [ t.enum [ "memory" ] t.enum [ "sessions" ] ]); - }; store = lib.mkOption { type = t.submodule { options = { driver = lib.mkOption { @@ -377,16 +313,6 @@ in path = lib.mkOption { type = t.str; }; - vector = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - extensionPath = lib.mkOption { - type = t.str; - }; - }; }; - }; }; }; }; sync = lib.mkOption { @@ -702,16 +628,6 @@ in }; memorySearch = lib.mkOption { type = t.submodule { options = { - cache = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - maxEntries = lib.mkOption { - type = t.int; - }; - }; }; - }; chunking = lib.mkOption { type = t.submodule { options = { overlap = lib.mkOption { @@ -725,15 +641,8 @@ in enabled = lib.mkOption { type = t.bool; }; - experimental = lib.mkOption { - type = t.submodule { options = { - sessionMemory = lib.mkOption { - type = t.bool; - }; - }; }; - }; fallback = lib.mkOption { - type = t.oneOf [ t.enum [ "openai" ] t.enum [ "gemini" ] t.enum [ "local" ] t.enum [ "none" ] ]; + type = t.oneOf [ t.enum [ "openai" ] t.enum [ "none" ] ]; }; local = lib.mkOption { type = t.submodule { options = { @@ -749,26 +658,10 @@ in type = t.str; }; provider = lib.mkOption { - type = t.oneOf [ t.enum [ "openai" ] t.enum [ "local" ] t.enum [ "gemini" ] ]; + type = t.oneOf [ t.enum [ "openai" ] t.enum [ "local" ] ]; }; query = lib.mkOption { type = t.submodule { options = { - hybrid = lib.mkOption { - type = t.submodule { options = { - candidateMultiplier = lib.mkOption { - type = t.int; - }; - enabled = lib.mkOption { - type = t.bool; - }; - textWeight = lib.mkOption { - type = t.number; - }; - vectorWeight = lib.mkOption { - type = t.number; - }; - }; }; - }; maxResults = lib.mkOption { type = t.int; }; @@ -785,33 +678,11 @@ in baseUrl = lib.mkOption { type = t.str; }; - batch = lib.mkOption { - type = t.submodule { options = { - concurrency = lib.mkOption { - type = t.int; - }; - enabled = lib.mkOption { - type = t.bool; - }; - pollIntervalMs = lib.mkOption { - type = t.int; - }; - timeoutMinutes = lib.mkOption { - type = t.int; - }; - wait = lib.mkOption { - type = t.bool; - }; - }; }; - }; headers = lib.mkOption { type = t.attrsOf (t.str); }; }; }; }; - sources = lib.mkOption { - type = t.listOf (t.oneOf [ t.enum [ "memory" ] t.enum [ "sessions" ] ]); - }; store = lib.mkOption { type = t.submodule { options = { driver = lib.mkOption { @@ -820,16 +691,6 @@ in path = lib.mkOption { type = t.str; }; - vector = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - extensionPath = lib.mkOption { - type = t.str; - }; - }; }; - }; }; }; }; sync = lib.mkOption { @@ -1063,47 +924,6 @@ in }; }; }; }; - exec = lib.mkOption { - type = t.submodule { options = { - applyPatch = lib.mkOption { - type = t.submodule { options = { - allowModels = lib.mkOption { - type = t.listOf (t.str); - }; - enabled = lib.mkOption { - type = t.bool; - }; - }; }; - }; - ask = lib.mkOption { - type = t.enum [ "off" "on-miss" "always" ]; - }; - backgroundMs = lib.mkOption { - type = t.int; - }; - cleanupMs = lib.mkOption { - type = t.int; - }; - host = lib.mkOption { - type = t.enum [ "sandbox" "gateway" "node" ]; - }; - node = lib.mkOption { - type = t.str; - }; - notifyOnExit = lib.mkOption { - type = t.bool; - }; - pathPrepend = lib.mkOption { - type = t.listOf (t.str); - }; - security = lib.mkOption { - type = t.enum [ "deny" "allowlist" "full" ]; - }; - timeoutSec = lib.mkOption { - type = t.int; - }; - }; }; - }; profile = lib.mkOption { type = t.oneOf [ t.enum [ "minimal" ] t.enum [ "coding" ] t.enum [ "messaging" ] t.enum [ "full" ] ]; }; @@ -1217,6 +1037,39 @@ in }; }); }; + bridge = lib.mkOption { + type = t.submodule { options = { + bind = lib.mkOption { + type = t.oneOf [ t.enum [ "auto" ] t.enum [ "lan" ] t.enum [ "tailnet" ] t.enum [ "loopback" ] ]; + }; + enabled = lib.mkOption { + type = t.bool; + }; + port = lib.mkOption { + type = t.int; + }; + tls = lib.mkOption { + type = t.submodule { options = { + autoGenerate = lib.mkOption { + type = t.bool; + }; + caPath = lib.mkOption { + type = t.str; + }; + certPath = lib.mkOption { + type = t.str; + }; + enabled = lib.mkOption { + type = t.bool; + }; + keyPath = lib.mkOption { + type = t.str; + }; + }; }; + }; + }; }; + }; + broadcast = lib.mkOption { type = t.submodule { options = { strategy = lib.mkOption { @@ -1247,268 +1100,60 @@ in }; enabled = lib.mkOption { type = t.bool; - }; - executablePath = lib.mkOption { - type = t.str; - }; - headless = lib.mkOption { - type = t.bool; - }; - noSandbox = lib.mkOption { - type = t.bool; - }; - profiles = lib.mkOption { - type = t.attrsOf (t.submodule { options = { - cdpPort = lib.mkOption { - type = t.int; - }; - cdpUrl = lib.mkOption { - type = t.str; - }; - color = lib.mkOption { - type = t.str; - }; - driver = lib.mkOption { - type = t.oneOf [ t.enum [ "clawd" ] t.enum [ "extension" ] ]; - }; - }; }); - }; - remoteCdpHandshakeTimeoutMs = lib.mkOption { - type = t.int; - }; - remoteCdpTimeoutMs = lib.mkOption { - type = t.int; - }; - }; }; - }; - - canvasHost = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - liveReload = lib.mkOption { - type = t.bool; - }; - port = lib.mkOption { - type = t.int; - }; - root = lib.mkOption { - type = t.str; - }; - }; }; - }; - - channels = lib.mkOption { - type = t.submodule { options = { - bluebubbles = lib.mkOption { - type = t.submodule { options = { - accounts = lib.mkOption { - type = t.attrsOf (t.submodule { options = { - allowFrom = lib.mkOption { - type = t.listOf (t.oneOf [ t.str t.number ]); - }; - blockStreaming = lib.mkOption { - type = t.bool; - }; - blockStreamingCoalesce = lib.mkOption { - type = t.submodule { options = { - idleMs = lib.mkOption { - type = t.int; - }; - maxChars = lib.mkOption { - type = t.int; - }; - minChars = lib.mkOption { - type = t.int; - }; - }; }; - }; - capabilities = lib.mkOption { - type = t.listOf (t.str); - }; - configWrites = lib.mkOption { - type = t.bool; - }; - dmHistoryLimit = lib.mkOption { - type = t.int; - }; - dmPolicy = lib.mkOption { - type = t.enum [ "pairing" "allowlist" "open" "disabled" ]; - }; - dms = lib.mkOption { - type = t.attrsOf (t.submodule { options = { - historyLimit = lib.mkOption { - type = t.int; - }; - }; }); - }; - enabled = lib.mkOption { - type = t.bool; - }; - groupAllowFrom = lib.mkOption { - type = t.listOf (t.oneOf [ t.str t.number ]); - }; - groupPolicy = lib.mkOption { - type = t.enum [ "open" "disabled" "allowlist" ]; - }; - groups = lib.mkOption { - type = t.attrsOf (t.submodule { options = { - requireMention = lib.mkOption { - type = t.bool; - }; - }; }); - }; - historyLimit = lib.mkOption { - type = t.int; - }; - mediaMaxMb = lib.mkOption { - type = t.int; - }; - name = lib.mkOption { - type = t.str; - }; - password = lib.mkOption { - type = t.str; - }; - sendReadReceipts = lib.mkOption { - type = t.bool; - }; - serverUrl = lib.mkOption { - type = t.str; - }; - textChunkLimit = lib.mkOption { - type = t.int; - }; - webhookPath = lib.mkOption { - type = t.str; - }; - }; }); - }; - actions = lib.mkOption { - type = t.submodule { options = { - addParticipant = lib.mkOption { - type = t.bool; - }; - edit = lib.mkOption { - type = t.bool; - }; - leaveGroup = lib.mkOption { - type = t.bool; - }; - reactions = lib.mkOption { - type = t.bool; - }; - removeParticipant = lib.mkOption { - type = t.bool; - }; - renameGroup = lib.mkOption { - type = t.bool; - }; - reply = lib.mkOption { - type = t.bool; - }; - sendAttachment = lib.mkOption { - type = t.bool; - }; - sendWithEffect = lib.mkOption { - type = t.bool; - }; - setGroupIcon = lib.mkOption { - type = t.bool; - }; - unsend = lib.mkOption { - type = t.bool; - }; - }; }; - }; - allowFrom = lib.mkOption { - type = t.listOf (t.oneOf [ t.str t.number ]); - }; - blockStreaming = lib.mkOption { - type = t.bool; - }; - blockStreamingCoalesce = lib.mkOption { - type = t.submodule { options = { - idleMs = lib.mkOption { - type = t.int; - }; - maxChars = lib.mkOption { - type = t.int; - }; - minChars = lib.mkOption { - type = t.int; - }; - }; }; - }; - capabilities = lib.mkOption { - type = t.listOf (t.str); - }; - configWrites = lib.mkOption { - type = t.bool; - }; - dmHistoryLimit = lib.mkOption { - type = t.int; - }; - dmPolicy = lib.mkOption { - type = t.enum [ "pairing" "allowlist" "open" "disabled" ]; - }; - dms = lib.mkOption { - type = t.attrsOf (t.submodule { options = { - historyLimit = lib.mkOption { - type = t.int; - }; - }; }); - }; - enabled = lib.mkOption { - type = t.bool; - }; - groupAllowFrom = lib.mkOption { - type = t.listOf (t.oneOf [ t.str t.number ]); - }; - groupPolicy = lib.mkOption { - type = t.enum [ "open" "disabled" "allowlist" ]; - }; - groups = lib.mkOption { - type = t.attrsOf (t.submodule { options = { - requireMention = lib.mkOption { - type = t.bool; - }; - }; }); - }; - historyLimit = lib.mkOption { - type = t.int; - }; - mediaMaxMb = lib.mkOption { + }; + executablePath = lib.mkOption { + type = t.str; + }; + headless = lib.mkOption { + type = t.bool; + }; + noSandbox = lib.mkOption { + type = t.bool; + }; + profiles = lib.mkOption { + type = t.attrsOf (t.submodule { options = { + cdpPort = lib.mkOption { type = t.int; }; - name = lib.mkOption { - type = t.str; - }; - password = lib.mkOption { + cdpUrl = lib.mkOption { type = t.str; }; - sendReadReceipts = lib.mkOption { - type = t.bool; - }; - serverUrl = lib.mkOption { + color = lib.mkOption { type = t.str; }; - textChunkLimit = lib.mkOption { - type = t.int; - }; - webhookPath = lib.mkOption { - type = t.str; + driver = lib.mkOption { + type = t.oneOf [ t.enum [ "clawd" ] t.enum [ "extension" ] ]; }; - }; }; + }; }); }; - defaults = lib.mkOption { - type = t.submodule { options = { - groupPolicy = lib.mkOption { - type = t.enum [ "open" "disabled" "allowlist" ]; - }; - }; }; + remoteCdpHandshakeTimeoutMs = lib.mkOption { + type = t.int; + }; + remoteCdpTimeoutMs = lib.mkOption { + type = t.int; + }; + }; }; + }; + + canvasHost = lib.mkOption { + type = t.submodule { options = { + enabled = lib.mkOption { + type = t.bool; + }; + liveReload = lib.mkOption { + type = t.bool; + }; + port = lib.mkOption { + type = t.int; }; + root = lib.mkOption { + type = t.str; + }; + }; }; + }; + + channels = lib.mkOption { + type = t.submodule { options = { discord = lib.mkOption { type = t.submodule { options = { accounts = lib.mkOption { @@ -2544,9 +2189,6 @@ in mediaMaxMb = lib.mkOption { type = t.number; }; - mode = lib.mkOption { - type = t.enum [ "socket" "http" ]; - }; name = lib.mkOption { type = t.str; }; @@ -2562,9 +2204,6 @@ in requireMention = lib.mkOption { type = t.bool; }; - signingSecret = lib.mkOption { - type = t.str; - }; slashCommand = lib.mkOption { type = t.submodule { options = { enabled = lib.mkOption { @@ -2600,9 +2239,6 @@ in userTokenReadOnly = lib.mkOption { type = t.bool; }; - webhookPath = lib.mkOption { - type = t.str; - }; }; }); }; actions = lib.mkOption { @@ -2740,9 +2376,6 @@ in mediaMaxMb = lib.mkOption { type = t.number; }; - mode = lib.mkOption { - type = t.enum [ "socket" "http" ]; - }; name = lib.mkOption { type = t.str; }; @@ -2758,9 +2391,6 @@ in requireMention = lib.mkOption { type = t.bool; }; - signingSecret = lib.mkOption { - type = t.str; - }; slashCommand = lib.mkOption { type = t.submodule { options = { enabled = lib.mkOption { @@ -2796,9 +2426,6 @@ in userTokenReadOnly = lib.mkOption { type = t.bool; }; - webhookPath = lib.mkOption { - type = t.str; - }; }; }; }; telegram = lib.mkOption { @@ -3450,48 +3077,6 @@ in }; }; }; - diagnostics = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - otel = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - endpoint = lib.mkOption { - type = t.str; - }; - flushIntervalMs = lib.mkOption { - type = t.int; - }; - headers = lib.mkOption { - type = t.attrsOf (t.str); - }; - logs = lib.mkOption { - type = t.bool; - }; - metrics = lib.mkOption { - type = t.bool; - }; - protocol = lib.mkOption { - type = t.oneOf [ t.enum [ "http/protobuf" ] t.enum [ "grpc" ] ]; - }; - sampleRate = lib.mkOption { - type = t.number; - }; - serviceName = lib.mkOption { - type = t.str; - }; - traces = lib.mkOption { - type = t.bool; - }; - }; }; - }; - }; }; - }; - discovery = lib.mkOption { type = t.submodule { options = { wideArea = lib.mkOption { @@ -3541,7 +3126,7 @@ in }; }; }; bind = lib.mkOption { - type = t.oneOf [ t.enum [ "auto" ] t.enum [ "lan" ] t.enum [ "loopback" ] t.enum [ "custom" ] ]; + type = t.oneOf [ t.enum [ "auto" ] t.enum [ "lan" ] t.enum [ "tailnet" ] t.enum [ "loopback" ] ]; }; controlUi = lib.mkOption { type = t.submodule { options = { @@ -3564,70 +3149,6 @@ in }; }; }; }; - responses = lib.mkOption { - type = t.submodule { options = { - enabled = lib.mkOption { - type = t.bool; - }; - files = lib.mkOption { - type = t.submodule { options = { - allowUrl = lib.mkOption { - type = t.bool; - }; - allowedMimes = lib.mkOption { - type = t.listOf (t.str); - }; - maxBytes = lib.mkOption { - type = t.int; - }; - maxChars = lib.mkOption { - type = t.int; - }; - maxRedirects = lib.mkOption { - type = t.int; - }; - pdf = lib.mkOption { - type = t.submodule { options = { - maxPages = lib.mkOption { - type = t.int; - }; - maxPixels = lib.mkOption { - type = t.int; - }; - minTextChars = lib.mkOption { - type = t.int; - }; - }; }; - }; - timeoutMs = lib.mkOption { - type = t.int; - }; - }; }; - }; - images = lib.mkOption { - type = t.submodule { options = { - allowUrl = lib.mkOption { - type = t.bool; - }; - allowedMimes = lib.mkOption { - type = t.listOf (t.str); - }; - maxBytes = lib.mkOption { - type = t.int; - }; - maxRedirects = lib.mkOption { - type = t.int; - }; - timeoutMs = lib.mkOption { - type = t.int; - }; - }; }; - }; - maxBodyBytes = lib.mkOption { - type = t.int; - }; - }; }; - }; }; }; }; }; }; @@ -3635,16 +3156,6 @@ in mode = lib.mkOption { type = t.oneOf [ t.enum [ "local" ] t.enum [ "remote" ] ]; }; - nodes = lib.mkOption { - type = t.submodule { options = { - allowCommands = lib.mkOption { - type = t.listOf (t.str); - }; - denyCommands = lib.mkOption { - type = t.listOf (t.str); - }; - }; }; - }; port = lib.mkOption { type = t.int; }; @@ -3669,9 +3180,6 @@ in sshTarget = lib.mkOption { type = t.str; }; - tlsFingerprint = lib.mkOption { - type = t.str; - }; token = lib.mkOption { type = t.str; }; @@ -3690,25 +3198,6 @@ in }; }; }; }; - tls = lib.mkOption { - type = t.submodule { options = { - autoGenerate = lib.mkOption { - type = t.bool; - }; - caPath = lib.mkOption { - type = t.str; - }; - certPath = lib.mkOption { - type = t.str; - }; - enabled = lib.mkOption { - type = t.bool; - }; - keyPath = lib.mkOption { - type = t.str; - }; - }; }; - }; }; }; }; @@ -4055,17 +3544,6 @@ in }; }; }; - meta = lib.mkOption { - type = t.submodule { options = { - lastTouchedAt = lib.mkOption { - type = t.str; - }; - lastTouchedVersion = lib.mkOption { - type = t.str; - }; - }; }; - }; - models = lib.mkOption { type = t.submodule { options = { mode = lib.mkOption { @@ -4074,14 +3552,11 @@ in providers = lib.mkOption { type = t.attrsOf (t.submodule { options = { api = lib.mkOption { - type = t.oneOf [ t.enum [ "openai-completions" ] t.enum [ "openai-responses" ] t.enum [ "anthropic-messages" ] t.enum [ "google-generative-ai" ] t.enum [ "github-copilot" ] t.enum [ "bedrock-converse-stream" ] ]; + type = t.oneOf [ t.enum [ "openai-completions" ] t.enum [ "openai-responses" ] t.enum [ "anthropic-messages" ] t.enum [ "google-generative-ai" ] t.enum [ "github-copilot" ] ]; }; apiKey = lib.mkOption { type = t.str; }; - auth = lib.mkOption { - type = t.oneOf [ t.enum [ "api-key" ] t.enum [ "aws-sdk" ] t.enum [ "oauth" ] t.enum [ "token" ] ]; - }; authHeader = lib.mkOption { type = t.bool; }; @@ -4094,7 +3569,7 @@ in models = lib.mkOption { type = t.listOf (t.submodule { options = { api = lib.mkOption { - type = t.oneOf [ t.enum [ "openai-completions" ] t.enum [ "openai-responses" ] t.enum [ "anthropic-messages" ] t.enum [ "google-generative-ai" ] t.enum [ "github-copilot" ] t.enum [ "bedrock-converse-stream" ] ]; + type = t.oneOf [ t.enum [ "openai-completions" ] t.enum [ "openai-responses" ] t.enum [ "anthropic-messages" ] t.enum [ "google-generative-ai" ] t.enum [ "github-copilot" ] ]; }; compat = lib.mkOption { type = t.submodule { options = { @@ -4206,13 +3681,6 @@ in }; }; }; }; - slots = lib.mkOption { - type = t.submodule { options = { - memory = lib.mkOption { - type = t.str; - }; - }; }; - }; }; }; }; @@ -4240,62 +3708,6 @@ in mainKey = lib.mkOption { type = t.str; }; - reset = lib.mkOption { - type = t.submodule { options = { - atHour = lib.mkOption { - type = t.int; - }; - idleMinutes = lib.mkOption { - type = t.int; - }; - mode = lib.mkOption { - type = t.oneOf [ t.enum [ "daily" ] t.enum [ "idle" ] ]; - }; - }; }; - }; - resetByType = lib.mkOption { - type = t.submodule { options = { - dm = lib.mkOption { - type = t.submodule { options = { - atHour = lib.mkOption { - type = t.int; - }; - idleMinutes = lib.mkOption { - type = t.int; - }; - mode = lib.mkOption { - type = t.oneOf [ t.enum [ "daily" ] t.enum [ "idle" ] ]; - }; - }; }; - }; - group = lib.mkOption { - type = t.submodule { options = { - atHour = lib.mkOption { - type = t.int; - }; - idleMinutes = lib.mkOption { - type = t.int; - }; - mode = lib.mkOption { - type = t.oneOf [ t.enum [ "daily" ] t.enum [ "idle" ] ]; - }; - }; }; - }; - thread = lib.mkOption { - type = t.submodule { options = { - atHour = lib.mkOption { - type = t.int; - }; - idleMinutes = lib.mkOption { - type = t.int; - }; - mode = lib.mkOption { - type = t.oneOf [ t.enum [ "daily" ] t.enum [ "idle" ] ]; - }; - }; }; - }; - }; }; - }; resetTriggers = lib.mkOption { type = t.listOf (t.str); }; @@ -4351,9 +3763,6 @@ in apiKey = lib.mkOption { type = t.str; }; - config = lib.mkOption { - type = t.attrsOf (t.anything); - }; enabled = lib.mkOption { type = t.bool; }; @@ -4464,30 +3873,15 @@ in }; }; }; }; - ask = lib.mkOption { - type = t.enum [ "off" "on-miss" "always" ]; - }; backgroundMs = lib.mkOption { type = t.int; }; cleanupMs = lib.mkOption { type = t.int; }; - host = lib.mkOption { - type = t.enum [ "sandbox" "gateway" "node" ]; - }; - node = lib.mkOption { - type = t.str; - }; notifyOnExit = lib.mkOption { type = t.bool; }; - pathPrepend = lib.mkOption { - type = t.listOf (t.str); - }; - security = lib.mkOption { - type = t.enum [ "deny" "allowlist" "full" ]; - }; timeoutSec = lib.mkOption { type = t.int; }; @@ -5115,21 +4509,8 @@ in maxResults = lib.mkOption { type = t.int; }; - perplexity = lib.mkOption { - type = t.submodule { options = { - apiKey = lib.mkOption { - type = t.str; - }; - baseUrl = lib.mkOption { - type = t.str; - }; - model = lib.mkOption { - type = t.str; - }; - }; }; - }; provider = lib.mkOption { - type = t.oneOf [ t.enum [ "brave" ] t.enum [ "perplexity" ] ]; + type = t.oneOf [ t.enum [ "brave" ] ]; }; timeoutSeconds = lib.mkOption { type = t.int; @@ -5152,7 +4533,7 @@ in update = lib.mkOption { type = t.submodule { options = { channel = lib.mkOption { - type = t.oneOf [ t.enum [ "stable" ] t.enum [ "beta" ] t.enum [ "dev" ] ]; + type = t.oneOf [ t.enum [ "stable" ] t.enum [ "beta" ] ]; }; checkOnStart = lib.mkOption { type = t.bool; From 215baa49e67b5bdbf935a8423a98394555cd0333 Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 20 Jan 2026 22:33:20 -0500 Subject: [PATCH 07/14] Enhance update-pins.sh to auto-update version strings The script now updates version strings in gateway and check derivations when syncing to a new release tag. This prevents CI failures caused by version mismatches between the generated config options and the golden file. Files updated: - nix/packages/clawdbot-gateway.nix - nix/checks/clawdbot-gateway-tests.nix - nix/checks/clawdbot-config-options.nix Co-Authored-By: Claude Opus 4.5 --- scripts/update-pins.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/update-pins.sh b/scripts/update-pins.sh index f683da0d..eea8854d 100755 --- a/scripts/update-pins.sh +++ b/scripts/update-pins.sh @@ -142,6 +142,18 @@ if [[ -z "$release_tag" ]]; then fi log "Latest app release tag with asset: $release_tag" +# Update version strings in gateway package and check derivations +gateway_version="${release_tag#v}" +log "Updating gateway version to: $gateway_version" + +gateway_file="$repo_root/nix/packages/clawdbot-gateway.nix" +tests_file="$repo_root/nix/checks/clawdbot-gateway-tests.nix" +options_file="$repo_root/nix/checks/clawdbot-config-options.nix" + +perl -0pi -e "s|version = \"[^\"]+\";|version = \"${gateway_version}\";|" "$gateway_file" +perl -0pi -e "s|version = \"[^\"]+\";|version = \"${gateway_version}\";|" "$tests_file" +perl -0pi -e "s|version = \"[^\"]+\";|version = \"${gateway_version}\";|" "$options_file" + app_url=$(printf '%s' "$release_json" | jq -r '[.[] | select([.assets[]?.name | (test("^(Clawdbot|Clawdis)-.*\\.zip$") and (test("dSYM") | not))] | any)][0].assets[] | select(.name | (test("^(Clawdbot|Clawdis)-.*\\.zip$") and (test("dSYM") | not))) | .browser_download_url' | head -n 1 || true) if [[ -z "$app_url" ]]; then echo "Failed to resolve Clawdbot app asset URL from latest release" >&2 @@ -227,7 +239,8 @@ if git diff --quiet; then fi log "Committing updated pins" -git add "$source_file" "$app_file" "$repo_root/nix/generated/clawdbot-config-options.nix" +git add "$source_file" "$app_file" "$gateway_file" "$tests_file" "$options_file" \ + "$repo_root/nix/generated/clawdbot-config-options.nix" git commit -F - <<'EOF' 🤖 codex: bump clawdbot pins (no-issue) @@ -235,6 +248,7 @@ What: - pin clawdbot source to latest upstream main - refresh macOS app pin to latest release asset - update source and app hashes +- update version strings in gateway and check derivations - regenerate config options from upstream schema Why: From 813b66a574c0a830469def07077a1e4399115973 Mon Sep 17 00:00:00 2001 From: lxc Date: Sat, 24 Jan 2026 15:42:19 -0500 Subject: [PATCH 08/14] chore: bump clawdbot to v2026.1.23 --- nix/packages/clawdbot-gateway.nix | 2 +- nix/sources/clawdbot-source.nix | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/packages/clawdbot-gateway.nix b/nix/packages/clawdbot-gateway.nix index 99c18779..f13669eb 100644 --- a/nix/packages/clawdbot-gateway.nix +++ b/nix/packages/clawdbot-gateway.nix @@ -38,7 +38,7 @@ in stdenv.mkDerivation (finalAttrs: { pname = "clawdbot-gateway"; - version = "2026.1.16-2"; + version = "2026.1.23"; src = if gatewaySrc != null then gatewaySrc else fetchFromGitHub sourceFetch; diff --git a/nix/sources/clawdbot-source.nix b/nix/sources/clawdbot-source.nix index afc59e90..7d59b055 100644 --- a/nix/sources/clawdbot-source.nix +++ b/nix/sources/clawdbot-source.nix @@ -2,7 +2,7 @@ { owner = "clawdbot"; repo = "clawdbot"; - rev = "be37b39782e0799ba5b9533561de6d128d50c863"; - hash = "sha256-y1ToqEcfl0yVAJkVld0k5AX5tztiE7yJt/F7Rhg+dAc="; - pnpmDepsHash = "sha256-NPQrkhhvAoIYzR1gopqsErps1K/HkfxmrPXpyMlN0Bc="; + rev = "c9e98376b3e5d3a2f3a1639be53bd850f6d3acbf"; + hash = "sha256-egAHjt6CHz79fStSg42opVPHjquurAa6FcGpNkQ0UtA="; + pnpmDepsHash = "sha256-N0rAUNutQ/zox1ZL6Lt/lwvXoPc5mbmW5mw3f0fSuKw="; } From 0dd8220343c11ddb7ae80a26857498892352b01a Mon Sep 17 00:00:00 2001 From: lxc Date: Sat, 24 Jan 2026 16:03:01 -0500 Subject: [PATCH 09/14] fix: use channels.telegram config format --- nix/modules/home-manager/clawdbot.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nix/modules/home-manager/clawdbot.nix b/nix/modules/home-manager/clawdbot.nix index ceb36f47..be9c44aa 100644 --- a/nix/modules/home-manager/clawdbot.nix +++ b/nix/modules/home-manager/clawdbot.nix @@ -36,11 +36,13 @@ let }; mkTelegramConfig = inst: lib.optionalAttrs inst.providers.telegram.enable { - telegram = { - enabled = true; - tokenFile = inst.providers.telegram.botTokenFile; - allowFrom = inst.providers.telegram.allowFrom; - groups = inst.providers.telegram.groups; + channels = { + telegram = { + enabled = true; + tokenFile = inst.providers.telegram.botTokenFile; + allowFrom = inst.providers.telegram.allowFrom; + groups = inst.providers.telegram.groups; + }; }; }; From f5255b28f7f22db111909966c06e929dbb8eb6de Mon Sep 17 00:00:00 2001 From: lxc Date: Sat, 24 Jan 2026 16:03:42 -0500 Subject: [PATCH 10/14] fix: remove deprecated byProvider config key --- nix/modules/home-manager/clawdbot.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/nix/modules/home-manager/clawdbot.nix b/nix/modules/home-manager/clawdbot.nix index be9c44aa..5d03405d 100644 --- a/nix/modules/home-manager/clawdbot.nix +++ b/nix/modules/home-manager/clawdbot.nix @@ -50,7 +50,6 @@ let messages = { queue = { mode = inst.routing.queue.mode; - byProvider = inst.routing.queue.byProvider; }; }; }; From 0d6e9f06079a2c3f9e90fa582727ebf34141d778 Mon Sep 17 00:00:00 2001 From: lxc Date: Sat, 24 Jan 2026 17:11:28 -0500 Subject: [PATCH 11/14] fix: include extensions and templates in package build Fixes https://github.com/clawdbot/nix-clawdbot/issues/6 - Copy extensions/ directory for channel plugins (Telegram, Discord, etc.) - Copy docs/reference/templates/ for workspace initialization Without these directories, channel plugins don't load and workspace initialization fails with missing template errors. --- nix/scripts/gateway-install.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nix/scripts/gateway-install.sh b/nix/scripts/gateway-install.sh index cd09ff94..552f75a8 100755 --- a/nix/scripts/gateway-install.sh +++ b/nix/scripts/gateway-install.sh @@ -4,6 +4,18 @@ mkdir -p "$out/lib/clawdbot" "$out/bin" cp -r dist node_modules package.json ui "$out/lib/clawdbot/" +# Copy extensions directory for channel plugins (telegram, discord, etc.) +# See: https://github.com/clawdbot/nix-clawdbot/issues/6 +if [ -d "extensions" ]; then + cp -r extensions "$out/lib/clawdbot/" +fi + +# Copy docs/reference/templates for workspace initialization +if [ -d "docs/reference/templates" ]; then + mkdir -p "$out/lib/clawdbot/docs/reference" + cp -r docs/reference/templates "$out/lib/clawdbot/docs/reference/" +fi + if [ -z "${STDENV_SETUP:-}" ]; then echo "STDENV_SETUP is not set" >&2 exit 1 From 0f0ac27c714f6dce86c7e3e0f560e238849a31ea Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 27 Jan 2026 21:49:19 -0500 Subject: [PATCH 12/14] feat: add gateway runner app for FDA support Add launchd.useAppRunner option that creates a minimal .app bundle to run the gateway. This allows granting Full Disk Access (FDA) to the gateway process, which is required for iMessage support (imsg needs to read Messages.db). When enabled: 1. A 'Clawdbot Gateway Runner.app' is installed to ~/Applications/ 2. The LaunchAgent runs the app's executable directly 3. User grants FDA to the app in System Settings New files: - nix/packages/clawdbot-gateway-runner.nix: Minimal .app bundle builder New options: - launchd.useAppRunner: Enable the app runner (default: false) - launchd.appRunnerPath: Installation path for the app --- nix/modules/home-manager/clawdbot.nix | 109 +++++++++++++++++--- nix/packages/clawdbot-gateway-runner.nix | 125 +++++++++++++++++++++++ 2 files changed, 217 insertions(+), 17 deletions(-) create mode 100644 nix/packages/clawdbot-gateway-runner.nix diff --git a/nix/modules/home-manager/clawdbot.nix b/nix/modules/home-manager/clawdbot.nix index 5d03405d..06e3dc9a 100644 --- a/nix/modules/home-manager/clawdbot.nix +++ b/nix/modules/home-manager/clawdbot.nix @@ -239,6 +239,29 @@ let description = "launchd label for this instance."; }; + launchd.useAppRunner = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Run the gateway via a minimal .app bundle instead of a raw script. + This allows granting Full Disk Access (FDA) to the gateway process, + which is required for iMessage support (imsg needs to read Messages.db). + + When enabled: + 1. A "Clawdbot Gateway Runner.app" is installed to ~/Applications/ + 2. The LaunchAgent runs the app's executable directly + 3. Grant FDA to the app in System Settings > Privacy & Security > Full Disk Access + ''; + }; + + launchd.appRunnerPath = lib.mkOption { + type = lib.types.str; + default = if name == "default" + then "${homeDir}/Applications/Clawdbot Gateway Runner.app" + else "${homeDir}/Applications/Clawdbot Gateway Runner (${name}).app"; + description = "Installation path for the gateway runner app (when useAppRunner is enabled)."; + }; + systemd.enable = lib.mkOption { type = lib.types.bool; default = true; @@ -313,6 +336,8 @@ let launchd = { enable = cfg.launchd.enable; label = "com.steipete.clawdbot.gateway"; + useAppRunner = cfg.launchd.useAppRunner or false; + appRunnerPath = cfg.launchd.appRunnerPath or "${homeDir}/Applications/Clawdbot Gateway Runner.app"; }; systemd = { enable = cfg.systemd.enable; @@ -776,6 +801,23 @@ let exec "${gatewayPackage}/bin/clawdbot" "$@" ''; + + # Build gateway runner .app for FDA support (when useAppRunner is enabled) + gatewayRunnerApp = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.useAppRunner) ( + pkgs.callPackage ../../packages/clawdbot-gateway-runner.nix { + instanceName = name; + inherit gatewayWrapper homeDir; + stateDir = inst.stateDir; + logPath = inst.logPath; + gatewayPort = inst.gatewayPort; + configPath = inst.configPath; + } + ); + + appName = if name == "default" + then "Clawdbot Gateway Runner" + else "Clawdbot Gateway Runner (${name})"; + in { homeFile = { name = toRelative inst.configPath; @@ -791,31 +833,46 @@ let enable = true; config = { Label = inst.launchd.label; - ProgramArguments = [ - "${gatewayWrapper}/bin/clawdbot-gateway-${name}" - "gateway" - "--port" - "${toString inst.gatewayPort}" - ]; + ProgramArguments = + if inst.launchd.useAppRunner then [ + # Run the gateway runner app's executable directly + # This allows FDA to be granted to the .app bundle + "${inst.launchd.appRunnerPath}/Contents/MacOS/clawdbot-gateway-runner" + ] else [ + "${gatewayWrapper}/bin/clawdbot-gateway-${name}" + "gateway" + "--port" + "${toString inst.gatewayPort}" + ]; RunAtLoad = true; KeepAlive = true; WorkingDirectory = inst.stateDir; StandardOutPath = inst.logPath; StandardErrorPath = inst.logPath; - EnvironmentVariables = { - HOME = homeDir; - CLAWDBOT_CONFIG_PATH = inst.configPath; - CLAWDBOT_STATE_DIR = inst.stateDir; - CLAWDBOT_IMAGE_BACKEND = "sips"; - CLAWDBOT_NIX_MODE = "1"; - # Backward-compatible env names (gateway still uses CLAWDIS_* in some builds). - CLAWDIS_CONFIG_PATH = inst.configPath; - CLAWDIS_STATE_DIR = inst.stateDir; - CLAWDIS_IMAGE_BACKEND = "sips"; - CLAWDIS_NIX_MODE = "1"; + EnvironmentVariables = { + HOME = homeDir; + CLAWDBOT_CONFIG_PATH = inst.configPath; + CLAWDBOT_STATE_DIR = inst.stateDir; + CLAWDBOT_IMAGE_BACKEND = "sips"; + CLAWDBOT_NIX_MODE = "1"; + # Backward-compatible env names (gateway still uses CLAWDIS_* in some builds). + CLAWDIS_CONFIG_PATH = inst.configPath; + CLAWDIS_STATE_DIR = inst.stateDir; + CLAWDIS_IMAGE_BACKEND = "sips"; + CLAWDIS_NIX_MODE = "1"; + }; }; }; }; + + # Install the gateway runner app when useAppRunner is enabled + appRunnerInstall = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.useAppRunner) { + name = toRelative inst.launchd.appRunnerPath; + value = { + source = "${gatewayRunnerApp}/Applications/${appName}.app"; + recursive = true; + force = true; + }; }; systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) { @@ -867,6 +924,7 @@ let instanceConfigs = lib.mapAttrsToList mkInstanceConfig enabledInstances; appInstalls = lib.filter (item: item != null) (map (item: item.appInstall) instanceConfigs); + appRunnerInstalls = lib.filter (item: item != {}) (map (item: item.appRunnerInstall) instanceConfigs); appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) {} instanceConfigs; @@ -1119,6 +1177,22 @@ in { description = "Run Clawdbot gateway via launchd (macOS)."; }; + launchd.useAppRunner = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Run the gateway via a minimal .app bundle instead of a raw script. + This allows granting Full Disk Access (FDA) to the gateway process, + which is required for iMessage support. + ''; + }; + + launchd.appRunnerPath = lib.mkOption { + type = lib.types.str; + default = "${homeDir}/Applications/Clawdbot Gateway Runner.app"; + description = "Installation path for the gateway runner app."; + }; + systemd.enable = lib.mkOption { type = lib.types.bool; default = true; @@ -1175,6 +1249,7 @@ in { }; }) // (lib.listToAttrs appInstalls) + // (lib.listToAttrs (lib.filter (x: x != {}) (map (item: item.appRunnerInstall) instanceConfigs))) // documentsFiles // skillFiles // pluginSkillsFiles diff --git a/nix/packages/clawdbot-gateway-runner.nix b/nix/packages/clawdbot-gateway-runner.nix new file mode 100644 index 00000000..c7086bb6 --- /dev/null +++ b/nix/packages/clawdbot-gateway-runner.nix @@ -0,0 +1,125 @@ +{ lib +, stdenvNoCC +, writeShellScript +}: + +# Creates a minimal .app bundle that runs the clawdbot gateway. +# This .app can be granted Full Disk Access in System Preferences, +# which is required for iMessage support (imsg needs to read Messages.db). +# +# The LaunchAgent runs this .app's executable directly, passing environment +# variables (CLAWDBOT_CONFIG_PATH, CLAWDBOT_STATE_DIR, etc.) and handling +# stdout/stderr logging. +# +# Usage: +# 1. Build and install this .app to ~/Applications/ +# 2. Grant FDA in System Settings > Privacy & Security > Full Disk Access +# 3. The LaunchAgent will run the app's executable with proper env vars + +{ instanceName ? "default" +, gatewayWrapper # The gateway wrapper script (with env vars, plugin paths, etc.) +, stateDir # Used for working directory context +, logPath # Informational (actual logging handled by LaunchAgent) +, gatewayPort ? 18789 +, homeDir ? "" # Home directory for env var fallback +, configPath ? "" # Config path for env var fallback +}: + +let + appName = if instanceName == "default" + then "Clawdbot Gateway Runner" + else "Clawdbot Gateway Runner (${instanceName})"; + + bundleId = if instanceName == "default" + then "com.clawdbot.gateway-runner" + else "com.clawdbot.gateway-runner.${instanceName}"; + + # The actual script that runs inside the .app + # Environment variables (CLAWDBOT_CONFIG_PATH, etc.) are set by the LaunchAgent + # stdout/stderr are handled by the LaunchAgent's StandardOutPath/StandardErrorPath + runnerScript = writeShellScript "clawdbot-gateway-runner" '' + #!/bin/bash + set -euo pipefail + + # The LaunchAgent sets these env vars, but provide fallbacks just in case + export HOME="''${HOME:-${homeDir}}" + export CLAWDBOT_CONFIG_PATH="''${CLAWDBOT_CONFIG_PATH:-${configPath}}" + export CLAWDBOT_STATE_DIR="''${CLAWDBOT_STATE_DIR:-${stateDir}}" + export CLAWDBOT_IMAGE_BACKEND="''${CLAWDBOT_IMAGE_BACKEND:-sips}" + export CLAWDBOT_NIX_MODE="''${CLAWDBOT_NIX_MODE:-1}" + + # Run the gateway - stdout/stderr handled by LaunchAgent + exec "${gatewayWrapper}/bin/clawdbot-gateway-${instanceName}" \ + gateway \ + --port ${toString gatewayPort} + ''; + + infoPlist = '' + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + clawdbot-gateway-runner + CFBundleIconFile + AppIcon + CFBundleIdentifier + ${bundleId} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${appName} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 11.0 + LSUIElement + + NSHighResolutionCapable + + + +''; + +in stdenvNoCC.mkDerivation { + pname = "clawdbot-gateway-runner"; + version = "1.0.0"; + + dontUnpack = true; + dontBuild = true; + + installPhase = '' + runHook preInstall + + APP_DIR="$out/Applications/${appName}.app" + mkdir -p "$APP_DIR/Contents/MacOS" + mkdir -p "$APP_DIR/Contents/Resources" + + # Write Info.plist + cat > "$APP_DIR/Contents/Info.plist" << 'PLIST_EOF' +${infoPlist} +PLIST_EOF + + # Copy the runner script as the executable + cp "${runnerScript}" "$APP_DIR/Contents/MacOS/clawdbot-gateway-runner" + chmod +x "$APP_DIR/Contents/MacOS/clawdbot-gateway-runner" + + # Create PkgInfo + echo -n "APPL????" > "$APP_DIR/Contents/PkgInfo" + + runHook postInstall + ''; + + meta = with lib; { + description = "Clawdbot Gateway Runner - minimal .app for FDA permissions"; + homepage = "https://github.com/clawdbot/clawdbot"; + license = licenses.mit; + platforms = platforms.darwin; + }; +} From d7ba9efcb898ed1a3d9e8ad0cf12fd3a5c82d041 Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 27 Jan 2026 21:51:20 -0500 Subject: [PATCH 13/14] fix: flatten gateway runner package arguments for callPackage --- nix/packages/clawdbot-gateway-runner.nix | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/nix/packages/clawdbot-gateway-runner.nix b/nix/packages/clawdbot-gateway-runner.nix index c7086bb6..8c273733 100644 --- a/nix/packages/clawdbot-gateway-runner.nix +++ b/nix/packages/clawdbot-gateway-runner.nix @@ -1,6 +1,13 @@ { lib , stdenvNoCC , writeShellScript +, instanceName ? "default" +, gatewayWrapper +, stateDir +, logPath +, gatewayPort ? 18789 +, homeDir ? "" +, configPath ? "" }: # Creates a minimal .app bundle that runs the clawdbot gateway. @@ -16,15 +23,6 @@ # 2. Grant FDA in System Settings > Privacy & Security > Full Disk Access # 3. The LaunchAgent will run the app's executable with proper env vars -{ instanceName ? "default" -, gatewayWrapper # The gateway wrapper script (with env vars, plugin paths, etc.) -, stateDir # Used for working directory context -, logPath # Informational (actual logging handled by LaunchAgent) -, gatewayPort ? 18789 -, homeDir ? "" # Home directory for env var fallback -, configPath ? "" # Config path for env var fallback -}: - let appName = if instanceName == "default" then "Clawdbot Gateway Runner" From bcbd699bfe02d6c25d7e39ad90f7ca999efcf376 Mon Sep 17 00:00:00 2001 From: lxc Date: Tue, 27 Jan 2026 21:59:56 -0500 Subject: [PATCH 14/14] fix: copy gateway runner app instead of symlink for FDA FDA checks the actual binary path, not symlink targets. When home-manager symlinks the .app bundle to Nix store, FDA granted to ~/Applications/... doesn't apply because the actual binary is in /nix/store/... Now using activation script to copy the entire .app bundle, ensuring the binary exists at the FDA-granted path. --- nix/modules/home-manager/clawdbot.nix | 41 ++++++++++++++++++++------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/nix/modules/home-manager/clawdbot.nix b/nix/modules/home-manager/clawdbot.nix index 06e3dc9a..aadfc079 100644 --- a/nix/modules/home-manager/clawdbot.nix +++ b/nix/modules/home-manager/clawdbot.nix @@ -865,15 +865,16 @@ let }; }; - # Install the gateway runner app when useAppRunner is enabled - appRunnerInstall = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.useAppRunner) { - name = toRelative inst.launchd.appRunnerPath; - value = { - source = "${gatewayRunnerApp}/Applications/${appName}.app"; - recursive = true; - force = true; - }; - }; + # Gateway runner app source path (for activation script to copy) + # We use activation instead of home.file because home.file symlinks, + # and FDA requires the actual binary to be at the granted path, not a symlink. + appRunnerSource = lib.optionalString (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.useAppRunner) + "${gatewayRunnerApp}/Applications/${appName}.app"; + appRunnerDest = lib.optionalString (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.useAppRunner) + inst.launchd.appRunnerPath; + + # Kept for backwards compatibility but no longer used for app runner + appRunnerInstall = {}; systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) { "${inst.systemd.unitName}" = { @@ -925,6 +926,11 @@ let instanceConfigs = lib.mapAttrsToList mkInstanceConfig enabledInstances; appInstalls = lib.filter (item: item != null) (map (item: item.appInstall) instanceConfigs); appRunnerInstalls = lib.filter (item: item != {}) (map (item: item.appRunnerInstall) instanceConfigs); + + # Collect gateway runner app source/dest pairs for copy activation + appRunnerCopyPairs = lib.filter (pair: pair.source != "" && pair.dest != "") ( + map (item: { source = item.appRunnerSource; dest = item.appRunnerDest; }) instanceConfigs + ); appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) {} instanceConfigs; @@ -1249,7 +1255,8 @@ in { }; }) // (lib.listToAttrs appInstalls) - // (lib.listToAttrs (lib.filter (x: x != {}) (map (item: item.appRunnerInstall) instanceConfigs))) + # Note: Gateway runner app is installed via activation script (clawdbotGatewayRunnerCopy) + # to ensure it's copied (not symlinked) for FDA to work properly // documentsFiles // skillFiles // pluginSkillsFiles @@ -1296,6 +1303,20 @@ in { '' ); + # Copy gateway runner apps (not symlink) so FDA works properly + # FDA checks the actual binary path, not symlink targets + home.activation.clawdbotGatewayRunnerCopy = lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appRunnerCopyPairs != []) ( + lib.hm.dag.entryAfter [ "writeBoundary" ] '' + ${lib.concatMapStringsSep "\n" (pair: '' + # Remove existing (might be a symlink from previous home-manager) + $DRY_RUN_CMD rm -rf "${pair.dest}" + # Copy the app bundle (not symlink, so FDA works) + $DRY_RUN_CMD cp -R "${pair.source}" "${pair.dest}" + $DRY_RUN_CMD chmod -R u+w "${pair.dest}" + '') appRunnerCopyPairs} + '' + ); + systemd.user.services = lib.mkIf pkgs.stdenv.hostPlatform.isLinux ( lib.mkMerge (map (item: item.systemdService) instanceConfigs) );