Compare commits

..
Author SHA1 Message Date
Ayaan Zaidi 4a3238cf89 fix: honor Telegram proxy dispatcher (#4456) (thanks @spiceoogway) 2026-01-30 14:37:17 +05:30
Peter Steinberger 6af205a13a docs: update lore with final form 2026-01-30 07:26:07 +00:00
Ayaan Zaidi 9025da2296 fix: scope telegram skill commands per bot (#4360) (thanks @robhparker) 2026-01-30 12:00:29 +05:30
robhparkerandClaude Opus 4.5 c6ddc95fc0 fix(telegram): scope skill commands to bound agent per bot
registerTelegramNativeCommands() calls listSkillCommandsForAgents()
without passing agentIds, causing ALL agents' skill commands to be
registered on EVERY Telegram bot. When multiple agents share skill
names (e.g. two agents both have a "butler" skill), the shared `used`
Set in listSkillCommandsForAgents causes de-duplication suffixes
(_2, _3) and all commands appear on every bot regardless of agent
binding.

This fix uses the existing resolveAgentRoute() (already imported) to
find the bound agent for the current Telegram accountId, then passes
that agentId to listSkillCommandsForAgents(). The function already
accepts an optional agentIds parameter — it just wasn't wired from
the Telegram registration path.

Before: All agents' skill commands registered on every Telegram bot,
causing /butler_2, /housekeeper_2 dedup suffixes and potential
BOT_COMMANDS_TOO_MUCH errors when total exceeds 100.

After: Each Telegram bot only registers skill commands for its own
bound agent. No cross-agent dedup, no command limit overflow.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 11:58:23 +05:30
Nate 28f8d00e9f fix: update install URLs from clawd.bot to openclaw.ai 2026-01-30 00:19:20 -06:00
Manik VahsithandPeter Steinberger 5e635c9656 feat: add Kimi K2.5 model to synthetic catalog (#4407)
* feat: add Kimi K2.5 model to synthetic catalog

Add hf:moonshotai/Kimi-K2.5 to the synthetic model catalog.
This model is available via dev.synthetic.new API.

- 256k context window
- 8192 max tokens
- Supports reasoning

* chore: fix formatting in onboard-helpers.ts

* fix: update config candidate ordering test (#4407) (thanks @manikv12)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-01-30 07:17:42 +01:00
Peter Steinberger 87267fad4f docs: move WhatsApp image below dashboard 2026-01-30 06:55:15 +01:00
Gustavo Madeira Santana 613724c26e Update index.md 2026-01-30 00:33:06 -05:00
11 changed files with 219 additions and 28 deletions

No files matched your search

+2 -2
View File
@@ -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' }}
+3
View File
@@ -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.
@@ -71,7 +72,9 @@ Status: stable.
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
### Fixes
- Telegram: use undici fetch for per-account proxy dispatcher. (#4456) Thanks @spiceoogway.
- 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.
+9 -8
View File
@@ -7,18 +7,15 @@ read_when:
> *"EXFOLIATE! EXFOLIATE!"* — A space lobster, probably
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/assets/openclaw-logo-text.png" />
<source media="(prefers-color-scheme: light)" srcset="/assets/openclaw-logo-text-dark.png" />
<img src="/assets/openclaw-logo-text.png" alt="OpenClaw" width="520" />
</picture>
</p>
<p align="center">
<img src="whatsapp-openclaw.jpg" alt="OpenClaw" width="420" />
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text-dark.png">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text.png" alt="OpenClaw" width="500">
</picture>
</p>
<p align="center">
<strong>Any OS + WhatsApp/Telegram/Discord/iMessage gateway for AI agents (Pi).</strong><br />
Plugins add Mattermost and more.
@@ -50,6 +47,10 @@ The dashboard is the browser Control UI for chat, config, nodes, sessions, and m
Local default: http://127.0.0.1:18789/
Remote access: [Web surfaces](/web) and [Tailscale](/gateway/tailscale)
<p align="center">
<img src="whatsapp-openclaw.jpg" alt="OpenClaw" width="420" />
</p>
## How it works
```
+47 -7
View File
@@ -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
🦞💙
+8
View File
@@ -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",
+6 -6
View File
@@ -64,12 +64,12 @@ export function randomToken(): string {
export function printWizardHeader(runtime: RuntimeEnv) {
const header = [
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
"██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░██",
"██░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░██",
"██░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██",
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
" 🦞 OPENCLAW 🦞 ",
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
"██░▄▄▄░██░▄▄░██░▄▄▄██░▀██░██░▄▄▀██░████░▄▄▀██░███░██",
"██░███░██░▀▀░██░▄▄▄██░█░█░██░█████░████░▀▀░██░█░█░██",
"██░▀▀▀░██░█████░▀▀▀██░██▄░██░▀▀▄██░▀▀░█░██░██▄▀▄▀▄██",
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
" 🦞 OPENCLAW 🦞 ",
" ",
].join("\n");
runtime.log(header);
+6 -2
View File
@@ -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 () => {
+82
View File
@@ -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<typeof registerTelegramNativeCommands>[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 });
});
});
+9 -1
View File
@@ -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" })
: [];
+45
View File
@@ -0,0 +1,45 @@
import { describe, expect, it, vi } from "vitest";
const { ProxyAgent, undiciFetch, proxyAgentSpy, getLastAgent } = vi.hoisted(() => {
const undiciFetch = vi.fn();
const proxyAgentSpy = vi.fn();
class ProxyAgent {
static lastCreated: ProxyAgent | undefined;
proxyUrl: string;
constructor(proxyUrl: string) {
this.proxyUrl = proxyUrl;
ProxyAgent.lastCreated = this;
proxyAgentSpy(proxyUrl);
}
}
return {
ProxyAgent,
undiciFetch,
proxyAgentSpy,
getLastAgent: () => ProxyAgent.lastCreated,
};
});
vi.mock("undici", () => ({
ProxyAgent,
fetch: undiciFetch,
}));
import { makeProxyFetch } from "./proxy.js";
describe("makeProxyFetch", () => {
it("uses undici fetch with ProxyAgent dispatcher", async () => {
const proxyUrl = "http://proxy.test:8080";
undiciFetch.mockResolvedValue({ ok: true });
const proxyFetch = makeProxyFetch(proxyUrl);
await proxyFetch("https://api.telegram.org/bot123/getMe");
expect(proxyAgentSpy).toHaveBeenCalledWith(proxyUrl);
expect(undiciFetch).toHaveBeenCalledWith(
"https://api.telegram.org/bot123/getMe",
expect.objectContaining({ dispatcher: getLastAgent() }),
);
});
});
+2 -2
View File
@@ -1,11 +1,11 @@
// @ts-nocheck
import { ProxyAgent } from "undici";
import { ProxyAgent, fetch as undiciFetch } from "undici";
import { wrapFetchWithAbortSignal } from "../infra/fetch.js";
export function makeProxyFetch(proxyUrl: string): typeof fetch {
const agent = new ProxyAgent(proxyUrl);
return wrapFetchWithAbortSignal((input: RequestInfo | URL, init?: RequestInit) => {
const base = init ? { ...init } : {};
return fetch(input, { ...base, dispatcher: agent });
return undiciFetch(input, { ...base, dispatcher: agent });
});
}