diff --git a/Dockerfile b/Dockerfile index 73ea109..fe03d0b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,23 @@ ENV NODE_PATH=/usr/local/lib/node_modules ENV NODE_ENV=production +# Install wacli (WhatsApp CLI) for the default OpenClaw wacli skill. +ARG WACLI_VERSION="0.15.0" +RUN ARCH=$(dpkg --print-architecture) && \ + case "$ARCH" in \ + amd64|arm64) WACLI_ARCH="$ARCH" ;; \ + *) echo "Unsupported architecture: $ARCH" && exit 1 ;; \ + esac && \ + TARBALL="wacli_${WACLI_VERSION}_linux_${WACLI_ARCH}.tar.gz" && \ + BASE_URL="https://github.com/openclaw/wacli/releases/download/v${WACLI_VERSION}" && \ + curl -fsSL "${BASE_URL}/${TARBALL}" -o "/tmp/${TARBALL}" && \ + curl -fsSL "${BASE_URL}/checksums.txt" -o /tmp/wacli_checksums.txt && \ + grep " ${TARBALL}$" /tmp/wacli_checksums.txt > /tmp/wacli_checksum.txt && \ + (cd /tmp && sha256sum -c wacli_checksum.txt) && \ + tar -xz -C /usr/local/bin -f "/tmp/${TARBALL}" wacli && \ + chmod +x /usr/local/bin/wacli && \ + rm "/tmp/${TARBALL}" /tmp/wacli_checksums.txt /tmp/wacli_checksum.txt + # Install ttyd (web terminal) - static binary from GitHub releases # ttyd is not available in bookworm repos, so we download the pre-built binary RUN ARCH=$(dpkg --print-architecture) && \ diff --git a/dappnode_package.json b/dappnode_package.json index b2f41f4..e1da346 100644 --- a/dappnode_package.json +++ b/dappnode_package.json @@ -91,8 +91,8 @@ "type": "service", "upstreamArg": "UPSTREAM_VERSION", "upstreamRepo": "openclaw/openclaw", - "upstreamVersion": "2026.6.11", - "version": "0.1.3", + "upstreamVersion": "2026.7.1", + "version": "0.1.4", "warnings": { "onRemove": "Removing this package will delete all your OpenClaw configuration, conversation history, and cached data. Make sure to create a backup first." } diff --git a/docker-compose.yml b/docker-compose.yml index 9b099c8..1e91dce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: context: . dockerfile: Dockerfile args: - UPSTREAM_VERSION: 2026.6.11 + UPSTREAM_VERSION: 2026.7.1 image: openclaw.dnp.dappnode.eth:0.1.0 container_name: DAppNodePackage-openclaw.dnp.dappnode.eth restart: unless-stopped diff --git a/entrypoint.sh b/entrypoint.sh index fb64f7d..e7263d4 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -52,24 +52,74 @@ try { if (!('dangerouslyDisableDeviceAuth' in cui)) { cui.dangerouslyDisableDeviceAuth = true; changed = true; } if (auth.token !== envToken) { auth.token = envToken; changed = true; } + function isObject(value) { + return value && typeof value === 'object' && !Array.isArray(value); + } + + function migrateTelegramStreaming(entry) { + if (!isObject(entry)) return false; + let entryChanged = false; + const legacyStreaming = entry.streaming; + const streaming = isObject(legacyStreaming) ? legacyStreaming : {}; + + if ('streaming' in entry && !isObject(legacyStreaming)) { + if (typeof legacyStreaming === 'boolean') { + streaming.mode = legacyStreaming ? 'partial' : 'off'; + } else if (legacyStreaming != null) { + streaming.mode = String(legacyStreaming); + } + entry.streaming = streaming; + entryChanged = true; + } + if ('streamMode' in entry) { + if (!streaming.mode) streaming.mode = entry.streamMode; + delete entry.streamMode; + entryChanged = true; + } + if ('chunkMode' in entry) { + if (!('chunkMode' in streaming)) streaming.chunkMode = entry.chunkMode; + delete entry.chunkMode; + entryChanged = true; + } + if ('draftChunk' in entry) { + const preview = isObject(streaming.preview) ? streaming.preview : {}; + if (!('chunk' in preview)) preview.chunk = entry.draftChunk; + streaming.preview = preview; + delete entry.draftChunk; + entryChanged = true; + } + if ('blockStreaming' in entry) { + const block = isObject(streaming.block) ? streaming.block : {}; + if (!('enabled' in block)) block.enabled = entry.blockStreaming; + streaming.block = block; + delete entry.blockStreaming; + entryChanged = true; + } + if ('blockStreamingCoalesce' in entry) { + const block = isObject(streaming.block) ? streaming.block : {}; + if (!('coalesce' in block)) block.coalesce = entry.blockStreamingCoalesce; + streaming.block = block; + delete entry.blockStreamingCoalesce; + entryChanged = true; + } + if (entryChanged) entry.streaming = streaming; + return entryChanged; + } + // Migrate legacy channel keys that openclaw doctor --fix cannot fully resolve in one pass: - // streamMode (string) → streaming (string) - // streaming (boolean) → streaming (string: true→\"partial\", false→\"off\") + // Telegram streamMode / scalar streaming / flat chunk keys → streaming.{mode,chunkMode,preview.chunk,block.*} // discord.botToken → discord.token (Telegram keeps botToken; Discord uses token) // discord guild channel: allow (bool) → enabled (bool) const channels = config.channels || {}; for (const [chName, ch] of Object.entries(channels)) { if (!ch || typeof ch !== 'object') continue; - // streamMode → streaming string - if ('streamMode' in ch) { - ch.streaming = ch.streamMode; - delete ch.streamMode; - changed = true; - } - // boolean streaming → string - if (typeof ch.streaming === 'boolean') { - ch.streaming = ch.streaming ? 'partial' : 'off'; - changed = true; + if (chName === 'telegram') { + if (migrateTelegramStreaming(ch)) changed = true; + if (ch.accounts && typeof ch.accounts === 'object') { + for (const account of Object.values(ch.accounts)) { + if (migrateTelegramStreaming(account)) changed = true; + } + } } // Discord: botToken is not valid — migrate to token (plain string) if (chName === 'discord' && 'botToken' in ch) { @@ -104,6 +154,17 @@ fi # --------------------------------------------------------------------------- # Run doctor --fix for any remaining migrations not handled above # --------------------------------------------------------------------------- +echo "Pruning stale OpenClaw session entries..." +MAIN_SESSION_STORE="$OPENCLAW_DIR/agents/main/sessions/sessions.json" +mkdir -p "$(dirname "$MAIN_SESSION_STORE")" +OPENCLAW_STATE_DIR="$OPENCLAW_DIR" openclaw sessions cleanup --store "$MAIN_SESSION_STORE" --enforce --fix-missing || true +if [ -d "$OPENCLAW_DIR/agents" ]; then + find "$OPENCLAW_DIR/agents" -mindepth 3 -maxdepth 3 -path "*/sessions/sessions.json" -print | while IFS= read -r SESSION_STORE; do + [ "$SESSION_STORE" = "$MAIN_SESSION_STORE" ] && continue + OPENCLAW_STATE_DIR="$OPENCLAW_DIR" openclaw sessions cleanup --store "$SESSION_STORE" --enforce --fix-missing || true + done +fi + echo "Running openclaw doctor --fix..." OPENCLAW_STATE_DIR="$OPENCLAW_DIR" openclaw doctor --fix || true diff --git a/setup-wizard/index.html b/setup-wizard/index.html index 115dae2..3449c21 100644 --- a/setup-wizard/index.html +++ b/setup-wizard/index.html @@ -336,6 +336,48 @@ color: var(--error); } + .nexus-auth-panel { + background: rgba(102, 126, 234, 0.08); + border: 1px solid rgba(102, 126, 234, 0.28); + border-radius: 10px; + padding: 1rem; + margin-bottom: 1.25rem; + } + + .nexus-auth-panel .btn { + width: 100%; + } + + .nexus-auth-note { + color: var(--text-muted); + font-size: 0.82rem; + line-height: 1.45; + margin-top: 0.75rem; + } + + .nexus-auth-result { + display: none; + margin-top: 0.75rem; + padding: 10px 12px; + border-radius: 8px; + font-size: 0.85rem; + line-height: 1.45; + } + + .nexus-auth-result.ok { + display: block; + background: rgba(52, 211, 153, 0.12); + border: 1px solid rgba(52, 211, 153, 0.3); + color: var(--success); + } + + .nexus-auth-result.fail { + display: block; + background: rgba(248, 113, 113, 0.12); + border: 1px solid rgba(248, 113, 113, 0.3); + color: var(--error); + } + /* Review */ .config-preview { background: var(--card); @@ -1236,20 +1278,24 @@

Configuration Saved!

(async () => { try { const resp = await fetch("/api/config"); - if (!resp.ok) return; - existingConfig = await resp.json(); - const providers = existingConfig.models && existingConfig.models.providers ? existingConfig.models.providers : {}; - existingProviderIds = Object.keys(providers); + if (resp.ok) { + existingConfig = await resp.json(); + const providers = existingConfig.models && existingConfig.models.providers ? existingConfig.models.providers : {}; + existingProviderIds = Object.keys(providers); - if (existingProviderIds.length > 0) { - goTo("home"); - } else { - // Config exists but no providers yet — just prefill and continue - document.getElementById("existing-config-banner").innerHTML = - ''; - prefillFromConfig(existingConfig); + if (existingProviderIds.length > 0) { + goTo("home"); + } else { + // Config exists but no providers yet — just prefill and continue + document.getElementById("existing-config-banner").innerHTML = + ''; + prefillFromConfig(existingConfig); + } } } catch { } + finally { + await handleNexusAuthRedirect(); + } })(); function prefillFromConfig(cfg) { @@ -1427,7 +1473,7 @@

Configuration Saved!

dmPolicy: "pairing", groupPolicy: "allowlist", groups: { "*": { requireMention: true } }, - streaming: "partial", + streaming: { mode: "partial" }, }; if (telegramUsers) { config.channels.telegram.allowFrom = telegramUsers.split(",").map(s => s.trim()).filter(Boolean); @@ -1596,13 +1642,18 @@

Configure DappNode Nexus

The gateway for AI builders who value privacy. Access top AI models through a single OpenAI-compatible API — filtered by privacy, speed, and reasoning level.
+
+ +
Creates a Nexus API key for this OpenClaw instance and fills it below. Manage your keys.
+
+
-
Found in your Nexus account under API keys.
+
Use Nexus login above, or paste a key from your Nexus account under API keys.