From 8255e4649ccdd6e0efab55d3a61f55618818d3ee Mon Sep 17 00:00:00 2001 From: Clawd Date: Wed, 21 Jan 2026 19:12:36 +0000 Subject: [PATCH 1/2] fix(mac): default to universal binary for distribution builds Closes #1393 The distribution script (package-mac-dist.sh) now defaults BUILD_ARCHS to 'all', producing universal binaries that run natively on both Apple Silicon and Intel Macs. Previously, the script inherited the host architecture default from package-mac-app.sh, which meant release builds done on ARM Macs only included ARM binaries. --- scripts/package-mac-dist.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/package-mac-dist.sh b/scripts/package-mac-dist.sh index 8ea373a8b..1cc17f1da 100755 --- a/scripts/package-mac-dist.sh +++ b/scripts/package-mac-dist.sh @@ -10,6 +10,9 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +# Default to universal binary for distribution builds (supports both Apple Silicon and Intel Macs) +export BUILD_ARCHS="${BUILD_ARCHS:-all}" + "$ROOT_DIR/scripts/package-mac-app.sh" APP="$ROOT_DIR/dist/Clawdbot.app" From 1cce83b21e973eb604fa136633692874d1a6133b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 22 Jan 2026 00:26:48 +0000 Subject: [PATCH 2/2] fix: refine model directive handling --- CHANGELOG.md | 1 + src/auto-reply/model.test.ts | 4 +- src/auto-reply/model.ts | 2 +- ...ists-allowlisted-models-model-list.test.ts | 26 +++++------ ...l-verbose-during-flight-run-toggle.test.ts | 7 ++- .../reply/directive-handling.model.ts | 45 ++++++++++++++----- 6 files changed, 55 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3bdd616..db9e20c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ Docs: https://docs.clawd.bot - 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/src/auto-reply/model.test.ts b/src/auto-reply/model.test.ts index a2bbf5f92..4a5aa7714 100644 --- a/src/auto-reply/model.test.ts +++ b/src/auto-reply/model.test.ts @@ -114,10 +114,10 @@ describe("extractModelDirective", () => { }); describe("edge cases", () => { - it("preserves spacing when /model is followed by a path segment", () => { + it("absorbs path-like segments when /model includes extra slashes", () => { const result = extractModelDirective("thats not /model gpt-5/tmp/hello"); expect(result.hasDirective).toBe(true); - expect(result.cleaned).toBe("thats not /hello"); + expect(result.cleaned).toBe("thats not"); }); it("handles alias with special regex characters", () => { diff --git a/src/auto-reply/model.ts b/src/auto-reply/model.ts index 46ea44db1..24dd8ea0c 100644 --- a/src/auto-reply/model.ts +++ b/src/auto-reply/model.ts @@ -14,7 +14,7 @@ export function extractModelDirective( if (!body) return { cleaned: "", hasDirective: false }; const modelMatch = body.match( - /(?:^|\s)\/models?(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)?)?/i, + /(?:^|\s)\/models?(?=$|\s|:)\s*:?\s*([A-Za-z0-9_.:@-]+(?:\/[A-Za-z0-9_.:@-]+)*)?/i, ); const aliases = (options?.aliases ?? []).map((alias) => alias.trim()).filter(Boolean); diff --git a/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.test.ts b/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.test.ts index ffac7deab..45a994432 100644 --- a/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.lists-allowlisted-models-model-list.test.ts @@ -84,9 +84,9 @@ describe("directive behavior", () => { ); const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("Pick: /model <#> or /model "); - expect(text).toContain("anthropic/claude-opus-4-5"); - expect(text).toContain("openai/gpt-4.1-mini"); + expect(text).toContain("Model listing moved."); + expect(text).toContain("Use: /models (providers) or /models (models)"); + expect(text).toContain("Switch: /model "); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); }); @@ -115,9 +115,9 @@ describe("directive behavior", () => { ); const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("Pick: /model <#> or /model "); - expect(text).toContain("anthropic/claude-opus-4-5"); - expect(text).toContain("openai/gpt-4.1-mini"); + expect(text).toContain("Current: anthropic/claude-opus-4-5"); + expect(text).toContain("Browse: /models (providers) or /models (models)"); + expect(text).toContain("More: /model status"); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); }); @@ -150,10 +150,9 @@ describe("directive behavior", () => { ); const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("anthropic/claude-opus-4-5"); - expect(text).toContain("openai/gpt-4.1-mini"); - expect(text).toContain("minimax/MiniMax-M2.1"); - expect(text).toContain("xai/grok-4"); + expect(text).toContain("Model listing moved."); + expect(text).toContain("Use: /models (providers) or /models (models)"); + expect(text).toContain("Switch: /model "); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); }); @@ -202,9 +201,9 @@ describe("directive behavior", () => { ); const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("anthropic/claude-opus-4-5"); - expect(text).toContain("openai/gpt-4.1-mini"); - expect(text).toContain("minimax/MiniMax-M2.1"); + expect(text).toContain("Model listing moved."); + expect(text).toContain("Use: /models (providers) or /models (models)"); + expect(text).toContain("Switch: /model "); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); }); @@ -231,6 +230,7 @@ describe("directive behavior", () => { ); const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toContain("Model listing moved."); expect(text).not.toContain("missing (missing)"); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); diff --git a/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.test.ts b/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.test.ts index 8ed532b0d..c271adef3 100644 --- a/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.test.ts +++ b/src/auto-reply/reply.directive.directive-behavior.updates-tool-verbose-during-flight-run-toggle.test.ts @@ -211,10 +211,9 @@ describe("directive behavior", () => { ); const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toContain("anthropic/claude-opus-4-5"); - expect(text).toContain("Pick: /model <#> or /model "); - expect(text).toContain("openai/gpt-4.1-mini"); - expect(text).not.toContain("claude-sonnet-4-1"); + expect(text).toContain("Current: anthropic/claude-opus-4-5"); + expect(text).toContain("Browse: /models (providers) or /models (models)"); + expect(text).toContain("More: /model status"); expect(runEmbeddedPiAgent).not.toHaveBeenCalled(); }); }); diff --git a/src/auto-reply/reply/directive-handling.model.ts b/src/auto-reply/reply/directive-handling.model.ts index 0f5782a6f..dd2f98914 100644 --- a/src/auto-reply/reply/directive-handling.model.ts +++ b/src/auto-reply/reply/directive-handling.model.ts @@ -341,16 +341,41 @@ export function resolveModelSelectionFromDirective(params: { if (resolved.selection) { const suggestion = `${resolved.selection.provider}/${resolved.selection.model}`; - return { - errorText: [ - `Unrecognized model: ${raw}`, - "", - `Did you mean: ${suggestion}`, - `Try: /model ${suggestion}`, - "", - "Browse: /models or /models ", - ].join("\n"), - }; + const rawHasSlash = raw.includes("/"); + const shouldAutoSelect = (() => { + if (!rawHasSlash) return true; + const slash = raw.indexOf("/"); + if (slash <= 0) return true; + const rawProvider = normalizeProviderId(raw.slice(0, slash)); + const rawFragment = raw + .slice(slash + 1) + .trim() + .toLowerCase(); + if (!rawFragment) return false; + const resolvedProvider = normalizeProviderId(resolved.selection.provider); + if (rawProvider !== resolvedProvider) return false; + const resolvedModel = resolved.selection.model.toLowerCase(); + return ( + resolvedModel.startsWith(rawFragment) || + resolvedModel.includes(rawFragment) || + rawFragment.startsWith(resolvedModel) + ); + })(); + + if (shouldAutoSelect) { + modelSelection = resolved.selection; + } else { + return { + errorText: [ + `Unrecognized model: ${raw}`, + "", + `Did you mean: ${suggestion}`, + `Try: /model ${suggestion}`, + "", + "Browse: /models or /models ", + ].join("\n"), + }; + } } }