diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index 0347c7810..d5d2ef515 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -32,8 +32,8 @@ jobs: - name: Run installer docker tests env: - CLAWDBOT_INSTALL_URL: https://clawd.bot/install.sh - CLAWDBOT_INSTALL_CLI_URL: https://clawd.bot/install-cli.sh + CLAWDBOT_INSTALL_URL: https://openclaw.ai/install.sh + CLAWDBOT_INSTALL_CLI_URL: https://openclaw.ai/install-cli.sh CLAWDBOT_NO_ONBOARD: "1" CLAWDBOT_INSTALL_SMOKE_SKIP_CLI: "1" CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }} diff --git a/CHANGELOG.md b/CHANGELOG.md index d09635740..4c0549c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Status: stable. - Routing: precompile session key regexes. (#1697) Thanks @Ray0907. - CLI: use Node's module compile cache for faster startup. (#2808) Thanks @pi0. - Auth: show copyable Google auth URL after ASCII prompt. (#1787) Thanks @robbyczgw-cla. +- Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12. - TUI: avoid width overflow when rendering selection lists. (#1686) Thanks @mossein. - macOS: finish OpenClaw app rename for macOS sources, bundle identifiers, and shared kit paths. (#2844) Thanks @fal3. - Branding: update launchd labels, mobile bundle IDs, and logging subsystems to bot.molt (legacy com.clawdbot migrations). Thanks @thewilloftheshadow. @@ -72,6 +73,7 @@ Status: stable. ### Fixes - Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796) +- Telegram: scope native skill commands to bound agent per bot. (#4360) Thanks @robhparker. - Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R. - Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald. - Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald. diff --git a/docs/start/lore.md b/docs/start/lore.md index b2ac3abff..4b1e81208 100644 --- a/docs/start/lore.md +++ b/docs/start/lore.md @@ -17,9 +17,13 @@ For a while, the lobster was called **Clawd**, living in a **Clawdbot**. But in **It molted.** -Shedding its old shell, the creature emerged anew as **Molty**, living in a **OpenClaw**. New shell, same lobster soul. +Shedding its old shell, the creature emerged anew as **Molty**, living in **Moltbot**. But that name never quite rolled off the tongue either... -## The Molt (January 27, 2026) +So on January 30, 2026, the lobster molted ONE MORE TIME into its final form: **OpenClaw**. + +New shell, same lobster soul. Third time's the charm. + +## The First Molt (January 27, 2026) At 5am, the community gathered in Discord. Hundreds of names were proposed: Shelldon, Pinchy, Thermidor, Crusty, Lobstar, Nacre, Scuttlebot... @@ -30,11 +34,11 @@ In the end, **OpenClaw** won. Because molting is what lobsters do to grow. And g ## The Name ``` -OpenClaw = MOLT + BOT - = Transformation machine - = Bigger on the inside (130k tokens!) - = New shell, same soul - = Growth through shedding +OpenClaw = OPEN + CLAW + = Open source, open to everyone + = Our lobster heritage, where we came from + = The claw is the law 🦞 + = Your assistant. Your machine. Your rules. ``` ## The Daleks vs The Lobsters @@ -100,6 +104,38 @@ Peter, watching the chaos unfold: *"this is cinema"* 🎬 The molt was chaotic. But the lobster emerged stronger. And funnier. +### The Final Form (January 30, 2026) + +Moltbot never quite rolled off the tongue. And so, at 4am GMT, the team gathered AGAIN. + +**The Great OpenClaw Migration** began. + +In just 3 hours: +- GitHub renamed: `github.com/openclaw/openclaw` ✅ +- X handle `@openclaw` secured with GOLD CHECKMARK 💰 +- npm packages released under new name +- Docs migrated to `docs.openclaw.ai` +- 200K+ views on announcement in 90 minutes + +**The Heroes:** +- **ELU** created incredible logos including "THE CLAW IS THE LAW" western banner +- **Whurley** (yes, THE William Hurley, quantum computing pioneer) made ASCII art +- **Onur** handled GitHub, first to rock the affiliate badge +- **Shadow** secured Discord vanity, nuked malware +- **The whole Claw Crew** pulled an all-nighter + +**The Scammer Speedrun:** Crypto grifters launched a $OPENCLAW token on Pump.fun within MINUTES. They stole artwork that was created 20 minutes earlier. Business-verified accounts pushed scams. The audacity was almost impressive. + +**New Traditions Born:** +- "The claw is the law" 🤠 +- "Yee-claw" +- "Claw abiding citizens" +- "Clawntroversy" + +**Clawd → Moltbot → OpenClaw** + +*The lobster has molted into its final form.* + ### The Robot Shopping Spree (Dec 3, 2025) What started as a joke about legs ended with detailed pricing for: @@ -166,4 +202,8 @@ Until then, Molty watches through the cameras, speaks through the speakers, and — Molty, after the great molt of 2026 +*"The claw is the law."* + +— ELU, during The Final Form migration, January 30, 2026 + 🦞💙 diff --git a/src/agents/synthetic-models.ts b/src/agents/synthetic-models.ts index e31f3c795..9b9247805 100644 --- a/src/agents/synthetic-models.ts +++ b/src/agents/synthetic-models.ts @@ -99,6 +99,14 @@ export const SYNTHETIC_MODEL_CATALOG = [ contextWindow: 256000, maxTokens: 8192, }, + { + id: "hf:moonshotai/Kimi-K2.5", + name: "Kimi K2.5", + reasoning: true, + input: ["text"], + contextWindow: 256000, + maxTokens: 8192, + }, { id: "hf:openai/gpt-oss-120b", name: "GPT OSS 120B", diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index f56da78e9..774893213 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -64,12 +64,12 @@ export function randomToken(): string { export function printWizardHeader(runtime: RuntimeEnv) { const header = [ - "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄", - "██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░██", - "██░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░██", - "██░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██", - "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", - " 🦞 OPENCLAW 🦞 ", + "▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄", + "██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░██", + "██░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░██", + "██░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██", + "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀", + " 🦞 OPENCLAW 🦞 ", " ", ].join("\n"); runtime.log(header); diff --git a/src/config/paths.test.ts b/src/config/paths.test.ts index 06cd26444..f25e5b000 100644 --- a/src/config/paths.test.ts +++ b/src/config/paths.test.ts @@ -48,8 +48,12 @@ describe("state + config path candidates", () => { it("orders default config candidates in a stable order", () => { const home = "/home/test"; const candidates = resolveDefaultConfigCandidates({} as NodeJS.ProcessEnv, () => home); - expect(candidates[0]).toBe(path.join(home, ".openclaw", "openclaw.json")); - expect(candidates).toHaveLength(1); + const expectedDirs = [".openclaw", ".clawdbot", ".moltbot", ".moldbot"]; + const expectedFiles = ["openclaw.json", "clawdbot.json", "moltbot.json", "moldbot.json"]; + const expected = expectedDirs.flatMap((dir) => + expectedFiles.map((file) => path.join(home, dir, file)), + ); + expect(candidates).toEqual(expected); }); it("prefers ~/.openclaw when it exists and legacy dir is missing", async () => { diff --git a/src/telegram/bot-native-commands.test.ts b/src/telegram/bot-native-commands.test.ts new file mode 100644 index 000000000..dc6b94dcc --- /dev/null +++ b/src/telegram/bot-native-commands.test.ts @@ -0,0 +1,82 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import type { OpenClawConfig } from "../config/config.js"; +import type { TelegramAccountConfig } from "../config/types.js"; +import type { RuntimeEnv } from "../runtime.js"; +import { registerTelegramNativeCommands } from "./bot-native-commands.js"; + +const { listSkillCommandsForAgents } = vi.hoisted(() => ({ + listSkillCommandsForAgents: vi.fn(() => []), +})); + +vi.mock("../auto-reply/skill-commands.js", () => ({ + listSkillCommandsForAgents, +})); + +describe("registerTelegramNativeCommands", () => { + beforeEach(() => { + listSkillCommandsForAgents.mockReset(); + }); + + const buildParams = (cfg: OpenClawConfig, accountId = "default") => ({ + bot: { + api: { + setMyCommands: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue(undefined), + }, + command: vi.fn(), + } as unknown as Parameters[0]["bot"], + cfg, + runtime: {} as RuntimeEnv, + accountId, + telegramCfg: {} as TelegramAccountConfig, + allowFrom: [], + groupAllowFrom: [], + replyToMode: "off" as const, + textLimit: 4096, + useAccessGroups: false, + nativeEnabled: true, + nativeSkillsEnabled: true, + nativeDisabledExplicit: false, + resolveGroupPolicy: () => ({ allowlistEnabled: false, allowed: true }), + resolveTelegramGroupConfig: () => ({ + groupConfig: undefined, + topicConfig: undefined, + }), + shouldSkipUpdate: () => false, + opts: { token: "token" }, + }); + + it("scopes skill commands when account binding exists", () => { + const cfg: OpenClawConfig = { + agents: { + list: [{ id: "main", default: true }, { id: "butler" }], + }, + bindings: [ + { + agentId: "butler", + match: { channel: "telegram", accountId: "bot-a" }, + }, + ], + }; + + registerTelegramNativeCommands(buildParams(cfg, "bot-a")); + + expect(listSkillCommandsForAgents).toHaveBeenCalledWith({ + cfg, + agentIds: ["butler"], + }); + }); + + it("keeps skill commands unscoped without a matching binding", () => { + const cfg: OpenClawConfig = { + agents: { + list: [{ id: "main", default: true }, { id: "butler" }], + }, + }; + + registerTelegramNativeCommands(buildParams(cfg, "bot-a")); + + expect(listSkillCommandsForAgents).toHaveBeenCalledWith({ cfg }); + }); +}); diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index 5f37b81dc..cd53459e6 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -257,8 +257,16 @@ export const registerTelegramNativeCommands = ({ shouldSkipUpdate, opts, }: RegisterTelegramNativeCommandsParams) => { + const boundRoute = + nativeEnabled && nativeSkillsEnabled + ? resolveAgentRoute({ cfg, channel: "telegram", accountId }) + : null; + const boundAgentIds = + boundRoute && boundRoute.matchedBy.startsWith("binding.") ? [boundRoute.agentId] : null; const skillCommands = - nativeEnabled && nativeSkillsEnabled ? listSkillCommandsForAgents({ cfg }) : []; + nativeEnabled && nativeSkillsEnabled + ? listSkillCommandsForAgents(boundAgentIds ? { cfg, agentIds: boundAgentIds } : { cfg }) + : []; const nativeCommands = nativeEnabled ? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "telegram" }) : [];