Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ Existing sandboxes do not auto-upgrade when a newer NemoClaw release ships a new
```console
$ nemoclaw my-assistant status
...
Agent: OpenClaw v2026.5.18
Agent: OpenClaw v2026.4.24
...
```

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/regression-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ jobs:


# ── OpenShell version-pin E2E ──────────────────────────────
# Coverage guard for #3474. If a host has sticky OpenShell 0.0.45 on PATH
# but this NemoClaw release supports only <=0.0.44, install-openshell.sh
# Coverage guard for #3474. If a host has sticky OpenShell 0.0.40 on PATH
# but this NemoClaw release supports only <=0.0.39, install-openshell.sh
# must replace it with the pinned compatible release instead of hard-failing.
openshell-version-pin-e2e:
needs: select_regression_jobs
Expand Down
17 changes: 8 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,12 @@ RUN set -eu; \
sed -i 's/const baseLstat = await fs\.lstat(params\.installBaseDir)/const baseLstat = await fs.stat(params.installBaseDir)/' "$ipd_file"; \
sed -i 's/baseLstat\.isSymbolicLink()/false \/* nemoclaw: symlink check disabled, realpath guards containment *\//' "$ipd_file"; \
if grep -q 'fs\.lstat(params\.installBaseDir)' "$ipd_file"; then echo "ERROR: Patch 3b (install-package-dir) left lstat in assertInstallBaseStable" >&2; exit 1; fi; \
# --- Patch 5: bump default WS handshake timeout to 60s (#2484) --- \
# OpenClaw's WS connect handshake has a hard-coded short timeout on both \
# --- Patch 5: bump default WS handshake timeout 10s -> 60s (#2484) --- \
# OpenClaw's WS connect handshake has a hard-coded 10s timeout on both \
# client and server. Server-side connect-handler processing can exceed \
# that limit under load (multiple concurrent connects on slow CI infra), \
# 10s under load (multiple concurrent connects on slow CI infra), \
# causing `openclaw agent --json` to fail with "gateway timeout after \
# <timeout>ms" and TC-SBX-02 to hit its 90s SSH timeout. \
# 10000ms" and TC-SBX-02 to hit its 90s SSH timeout. \
# \
# Both env vars (OPENCLAW_HANDSHAKE_TIMEOUT_MS, \
# OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS) are clamped at the same \
Expand All @@ -219,12 +219,12 @@ RUN set -eu; \
# \
# Removal criteria: drop when openclaw fixes the underlying connect \
# latency, or exposes the timeout as an unbounded env override. \
hto_files="$(grep -RIlE --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)' "$OC_DIST")"; \
hto_files="$(grep -RIlE --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4' "$OC_DIST")"; \
test -n "$hto_files" || { echo "ERROR: handshake-timeout constant not found" >&2; exit 1; }; \
printf '%s\n' "$hto_files" | xargs sed -i -E 's#DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)#DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4#g'; \
if grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = (1e4|15e3)' "$OC_DIST"; then echo "ERROR: Patch 5 left a short timeout constant" >&2; exit 1; fi
printf '%s\n' "$hto_files" | xargs sed -i -E 's|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4|DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 6e4|g'; \
if grep -REq --include='*.js' 'DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 1e4' "$OC_DIST"; then echo "ERROR: Patch 5 left a 1e4 constant" >&2; exit 1; fi

# Patch OpenClaw's pinned 2026.5.18 compiled selection runtime to expose a
# Patch OpenClaw's pinned 2026.4.24 compiled selection runtime to expose a
# compact searchable tool catalog to the model while preserving the full
# effective tool set behind tool_call. NEMOCLAW_TOOL_CATALOG=0 disables this
# wrapper if an emergency rollback is needed. The script fails closed if the
Expand Down Expand Up @@ -358,7 +358,6 @@ ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \
NEMOCLAW_DISCORD_GUILDS_B64=${NEMOCLAW_DISCORD_GUILDS_B64} \
NEMOCLAW_TELEGRAM_CONFIG_B64=${NEMOCLAW_TELEGRAM_CONFIG_B64} \
NEMOCLAW_WECHAT_CONFIG_B64=${NEMOCLAW_WECHAT_CONFIG_B64} \
NEMOCLAW_OPENCLAW_WECHAT_PLUGIN_PREINSTALLED=1 \
NEMOCLAW_DISABLE_DEVICE_AUTH=${NEMOCLAW_DISABLE_DEVICE_AUTH} \
NEMOCLAW_PROXY_HOST=${NEMOCLAW_PROXY_HOST} \
NEMOCLAW_PROXY_PORT=${NEMOCLAW_PROXY_PORT} \
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ RUN printf '%s\n' \
# OpenClaw version: change the OPENCLAW_VERSION ARG default so CI rebuilds
# the base image on push to main, or use workflow_dispatch on base-image.yaml
# with the openclaw_version input for a one-off build without editing this file.
ARG OPENCLAW_VERSION=2026.5.18
ARG OPENCLAW_VERSION=2026.4.24

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

Expand All @@ -204,7 +204,7 @@ RUN --mount=type=bind,source=nemoclaw-blueprint/blueprint.yaml,target=/tmp/bluep
USER sandbox
WORKDIR /sandbox
# hadolint ignore=DL3059,DL4006
RUN openclaw plugins install '@tencent-weixin/openclaw-weixin@2.4.3' --pin \
RUN openclaw plugins install '@tencent-weixin/openclaw-weixin@2.4.2' --pin \
&& openclaw config set plugins.entries.openclaw-weixin.enabled true
# hadolint ignore=DL3002
USER root
Expand Down
6 changes: 3 additions & 3 deletions agents/hermes/Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ FROM node:22-trixie-slim@sha256:2d9f5c76c8f4dd36e8f253bee5d828a83a6c09f36188f0b0
ENV DEBIAN_FRONTEND=noninteractive

# Hermes version pinned for reproducibility.
# Calver tag v2026.5.16 = Hermes Agent v0.14.0.
ARG HERMES_VERSION=v2026.5.16
ARG HERMES_TARBALL_SHA256=c0a554050a50ee9a62f3fa5cd288a167ba5640c42d647d100cdea084b7294143
# Calver tag v2026.4.23 = semver 0.11.0.
ARG HERMES_VERSION=v2026.4.23
ARG HERMES_TARBALL_SHA256=1ee1be80a2112b7edc581770cee8858e725ba110cc423979cd7102492504bc6b
ARG HERMES_UV_EXTRAS="messaging web"
ARG UV_VERSION=0.11.8

Expand Down
2 changes: 1 addition & 1 deletion agents/hermes/config/hermes-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function buildHermesConfig(settings: HermesBuildSettings): Record<string,
},
};

// Hermes v2026.4.23+ reads Discord behavior from top-level `discord:`.
// Hermes v2026.4.23 reads Discord behavior from top-level `discord:`.
// Bot tokens and user allowlists stay in .env so config.yaml never carries
// real secrets or credential placeholders under platforms.discord.
if (settings.messaging.enabledChannels.has("discord")) {
Expand Down
2 changes: 1 addition & 1 deletion agents/hermes/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ homepage: "https://github.com/NousResearch/hermes-agent"
install_method: curl # curl install.sh | bash
binary_path: /usr/local/bin/hermes
version_command: "hermes --version"
expected_version: "2026.5.16"
expected_version: "2026.4.23"
gateway_command: "hermes gateway run"

# ── Health probe ────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion agents/openclaw/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ homepage: "https://openclaw.ai"
install_method: npm # npm install -g openclaw@<version>
binary_path: /usr/local/bin/openclaw
version_command: "openclaw --version"
expected_version: "2026.5.18"
expected_version: "2026.4.24"
gateway_command: "openclaw gateway run"

# ── Health probe ────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ Existing sandboxes do not auto-upgrade when a newer NemoClaw release ships a new
```console
$ nemoclaw my-assistant status
...
Agent: OpenClaw v2026.5.18
Agent: OpenClaw v2026.4.24
...
```

Expand Down
6 changes: 3 additions & 3 deletions nemoclaw-blueprint/blueprint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# SPDX-License-Identifier: Apache-2.0

version: "0.1.0"
min_openshell_version: "0.0.44"
max_openshell_version: "0.0.44"
min_openclaw_version: "2026.5.18"
min_openshell_version: "0.0.39"
max_openshell_version: "0.0.39"
min_openclaw_version: "2026.4.24"
# Mirrors the components.sandbox.image manifest digest below. Lets a
# downstream consumer (or release tooling) verify the blueprint declares
# a specific sandbox image without parsing the components tree, and
Expand Down
138 changes: 32 additions & 106 deletions nemoclaw-blueprint/openclaw-plugins/kimi-inference-compat/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,6 @@ function decodeToolCallArguments(value) {
return null;
}

function encodeToolCallArgumentsLike(original, command) {
if (typeof original === "string") return JSON.stringify({ command });
return { command };
}

function splitSafeExecCommand(command) {
if (typeof command !== "string") return null;
if (!command.includes(";")) return null;
Expand All @@ -156,7 +151,12 @@ function buildSplitToolCallId(originalId, index, command) {
return `${baseId}_split_${index + 1}_${command}`;
}

function getSafeCombinedExecToolCallFromBlock(toolCall) {
function getSafeCombinedExecToolCall(message) {
if (!message || typeof message !== "object") return null;
const content = message.content;
if (!Array.isArray(content) || content.length !== 1) return null;

const toolCall = content[0];
if (!toolCall || typeof toolCall !== "object" || Array.isArray(toolCall)) return null;
if (toolCall.type !== "toolCall" || toolCall.name !== "exec") return null;

Expand All @@ -173,103 +173,27 @@ function getSafeCombinedExecToolCallFromBlock(toolCall) {
return { commands, toolCall };
}

function getExecToolCallCommand(toolCall) {
if (!toolCall || typeof toolCall !== "object" || Array.isArray(toolCall)) return null;
if (toolCall.type !== "toolCall" || toolCall.name !== "exec") return null;
const args = decodeToolCallArguments(toolCall.arguments);
if (!args) return null;
const argKeys = Object.keys(args);
if (argKeys.length !== 1 || argKeys[0] !== "command" || typeof args.command !== "string") {
return null;
}
return args.command;
}
function applySafeExecSplitToMessage(message, split) {
if (!message || typeof message !== "object" || !split) return false;
const { commands, toolCall } = split;

function buildSplitToolCalls(toolCall, commands) {
return commands.map((command, index) => ({
message.content = commands.map((command, index) => ({
type: "toolCall",
id: buildSplitToolCallId(toolCall.id, index, command),
name: "exec",
arguments: { command },
}));
}

function dedupeSafeExecToolCalls(content) {
const seenSafeExecCommands = new Set();
const deduped = [];
for (const block of content) {
const command = getExecToolCallCommand(block);
if (SAFE_SPLIT_EXEC_COMMANDS.has(command)) {
if (seenSafeExecCommands.has(command)) continue;
seenSafeExecCommands.add(command);
}
deduped.push(block);
}
return deduped;
}

function rewriteSafeCombinedExecToolCallsInContent(content) {
if (!Array.isArray(content)) return { changed: false, content };

let changed = false;
const expanded = [];
for (const block of content) {
const split = getSafeCombinedExecToolCallFromBlock(block);
if (split) {
expanded.push(...buildSplitToolCalls(split.toolCall, split.commands));
changed = true;
} else {
expanded.push(block);
}
}
if (!changed) return { changed: false, content };

return { changed: true, content: dedupeSafeExecToolCalls(expanded) };
}

function applySafeExecSplitToMessage(message) {
if (!message || typeof message !== "object") return false;
const rewritten = rewriteSafeCombinedExecToolCallsInContent(message.content);
if (!rewritten.changed) return false;
message.content = rewritten.content;
if (message.stopReason === "stop") message.stopReason = "toolUse";
return true;
}

function applySafeExecSplitAtContentIndex(message, split) {
if (!message || typeof message !== "object" || !Array.isArray(message.content) || !split) {
return false;
}
const index = Number.isInteger(split.contentIndex) ? split.contentIndex : 0;
if (index < 0 || index >= message.content.length) return false;
const replacement = buildSplitToolCalls(split.toolCall, split.commands);
message.content = dedupeSafeExecToolCalls([
...message.content.slice(0, index),
...replacement,
...message.content.slice(index + 1),
]);
if (message.stopReason === "stop") message.stopReason = "toolUse";
return true;
}

function targetSplitCommandIndex(event, split) {
const rawIndex = Number.isInteger(event && event.contentIndex) ? event.contentIndex : 0;
const fallbackIndex = Math.min(Math.max(rawIndex, 0), split.commands.length - 1);
const content = event && event.partial && Array.isArray(event.partial.content)
? event.partial.content
: [];
const commandAtContentIndex = getExecToolCallCommand(content[rawIndex]);
const commandIndex = split.commands.findIndex((command) => command === commandAtContentIndex);
return commandIndex >= 0 ? commandIndex : fallbackIndex;
}

function rewriteSafeCombinedExecToolCallInMessage(message) {
return applySafeExecSplitToMessage(message);
return applySafeExecSplitToMessage(message, getSafeCombinedExecToolCall(message));
}

function getSafeCombinedExecToolCallFromEventDelta(event) {
if (!event || typeof event !== "object") return null;
if (event.type !== "toolcall_delta") return null;
if (event.type !== "toolcall_delta" || typeof event.delta !== "string") return null;
const partial = event.partial;
if (!partial || typeof partial !== "object" || !Array.isArray(partial.content)) return null;
const index = Number.isInteger(event.contentIndex) ? event.contentIndex : 0;
Expand All @@ -286,31 +210,33 @@ function getSafeCombinedExecToolCallFromEventDelta(event) {

const commands = splitSafeExecCommand(args.command);
if (!commands) return null;
return { commands, toolCall, contentIndex: index };
return { commands, toolCall };
}

function rewriteSafeCombinedExecToolCallInEvent(event) {
if (!event || typeof event !== "object") return false;
const deltaSplit = getSafeCombinedExecToolCallFromEventDelta(event);
let changed = false;
const split =
getSafeCombinedExecToolCall(event.partial) ||
getSafeCombinedExecToolCall(event.message) ||
getSafeCombinedExecToolCallFromEventDelta(event);
if (!split) return false;

const partialChanged = applySafeExecSplitToMessage(event.partial);
const messageChanged = applySafeExecSplitToMessage(event.message);
changed = partialChanged || messageChanged;

if (deltaSplit) {
if (!partialChanged) changed = applySafeExecSplitAtContentIndex(event.partial, deltaSplit) || changed;
if (!messageChanged) changed = applySafeExecSplitAtContentIndex(event.message, deltaSplit) || changed;
changed = true;
const targetIndex = targetSplitCommandIndex(event, deltaSplit);
const targetCommand = deltaSplit.commands[targetIndex];
event.delta = encodeToolCallArgumentsLike(event.delta, targetCommand);
if (event.toolCall && typeof event.toolCall === "object" && !Array.isArray(event.toolCall)) {
event.toolCall = buildSplitToolCalls(deltaSplit.toolCall, deltaSplit.commands)[targetIndex];
}
applySafeExecSplitToMessage(event.partial, split);
applySafeExecSplitToMessage(event.message, split);

if (event.type === "toolcall_delta" && typeof event.delta === "string") {
event.delta = JSON.stringify({ command: split.commands[0] });
}
if (event.toolCall && typeof event.toolCall === "object" && !Array.isArray(event.toolCall)) {
event.toolCall = {
type: "toolCall",
id: buildSplitToolCallId(split.toolCall.id, 0, split.commands[0]),
name: "exec",
arguments: { command: split.commands[0] },
};
}

return changed;
return true;
}

function wrapStreamFinalMessages(stream) {
Expand Down
6 changes: 3 additions & 3 deletions nemoclaw/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
"./dist/index.js"
],
"compat": {
"pluginApi": ">=2026.5.18",
"minGatewayVersion": "2026.5.18"
"pluginApi": ">=2026.4.24",
"minGatewayVersion": "2026.4.24"
},
"build": {
"openclawVersion": "2026.5.18"
"openclawVersion": "2026.4.24"
}
},
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions nemoclaw/src/package-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ const packageJson = JSON.parse(

describe("OpenClaw package metadata", () => {
it("declares the required external plugin compatibility fields", () => {
expect(packageJson.openclaw?.compat?.pluginApi).toBe(">=2026.5.18");
expect(packageJson.openclaw?.compat?.minGatewayVersion).toBe("2026.5.18");
expect(packageJson.openclaw?.build?.openclawVersion).toBe("2026.5.18");
expect(packageJson.openclaw?.compat?.pluginApi).toBe(">=2026.4.24");
expect(packageJson.openclaw?.compat?.minGatewayVersion).toBe("2026.4.24");
expect(packageJson.openclaw?.build?.openclawVersion).toBe("2026.4.24");
});
});
6 changes: 3 additions & 3 deletions scripts/brev-launchable-ci-cpu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# curl -fsSL https://raw.githubusercontent.com/NVIDIA/NemoClaw/<ref>/scripts/brev-launchable-ci-cpu.sh | bash
#
# Environment overrides:
# OPENSHELL_VERSION — OpenShell CLI release tag (default: v0.0.44)
# OPENSHELL_VERSION — OpenShell CLI release tag (default: v0.0.39)
# NEMOCLAW_REF — NemoClaw git ref to clone (default: main)
# NEMOCLAW_CLONE_DIR — Where to clone NemoClaw (default: ~/NemoClaw)
# SKIP_DOCKER_PULL — Set to 1 to skip Docker image pre-pulls
Expand All @@ -40,7 +40,7 @@
set -euo pipefail

# ── Configuration ────────────────────────────────────────────────────
OPENSHELL_VERSION="${OPENSHELL_VERSION:-v0.0.44}"
OPENSHELL_VERSION="${OPENSHELL_VERSION:-v0.0.39}"
NEMOCLAW_REF="${NEMOCLAW_REF:-main}"
TARGET_USER="${SUDO_USER:-$(id -un)}"
TARGET_HOME="$(getent passwd "$TARGET_USER" | cut -d: -f6)"
Expand Down Expand Up @@ -250,7 +250,7 @@ DOCKER_PULL_PID=""
if [[ "${SKIP_DOCKER_PULL:-0}" != "1" ]]; then
info "Pre-pulling Docker images in background..."
(
SUPERVISOR_TAG="${OPENSHELL_VERSION#v}" # v0.0.44 -> 0.0.44
SUPERVISOR_TAG="${OPENSHELL_VERSION#v}" # v0.0.39 -> 0.0.39
SUPERVISOR_IMAGE="ghcr.io/nvidia/openshell/supervisor:${SUPERVISOR_TAG}"

# Pull all images in parallel
Expand Down
Loading
Loading