diff --git a/.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs b/.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs index eb36da9628b..5e31f12edcd 100644 --- a/.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs +++ b/.agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs @@ -4,6 +4,7 @@ import { execFileSync } from "node:child_process"; import { readFileSync, writeFileSync } from "node:fs"; const repo = "openclaw/openclaw"; +const commitAssociationQueryBatchSize = 20; const excludedHandles = new Set(["openclaw", "clawsweeper", "claude", "codex", "steipete"]); const nonEditorialTypes = new Set([ "build", @@ -618,13 +619,25 @@ function graphql(query) { let lastError; for (let attempt = 0; attempt < 5; attempt += 1) { try { - return githubApi(["graphql", "-f", `query=${query}`]).data; + const response = githubApi(["graphql", "-f", `query=${query}`]); + if (response?.data && typeof response.data === "object") { + return response.data; + } + const errors = Array.isArray(response?.errors) + ? response.errors.map((error) => error?.message).filter(Boolean) + : []; + const detail = [...errors, response?.message].filter(Boolean).join("\n"); + throw new Error( + detail + ? `GitHub GraphQL response did not include data:\n${detail}` + : "GitHub GraphQL response did not include data.", + ); } catch (error) { lastError = error; const message = [error?.message, error?.stdout, error?.stderr].filter(Boolean).join("\n"); // Historical ranges batch hundreds of objects; only retry transient transport failures. if ( - !/(?:operation timed out|ECONNRESET|ETIMEDOUT|EAI_AGAIN|TLS handshake timeout|stream error: .*CANCEL|unexpected end of JSON input|upstream connect error|connection termination|error connecting to api\.github\.com|Unexpected token '<')/i.test( + !/(?:operation timed out|ECONNRESET|ETIMEDOUT|EAI_AGAIN|TLS handshake timeout|stream error: .*CANCEL|unexpected end of JSON input|upstream connect error|connection termination|connection reset by peer|error connecting to api\.github\.com|Unexpected token '<'|something went wrong|temporarily unavailable|internal server error|rate limit)/i.test( message, ) ) { @@ -657,8 +670,8 @@ function resolveAssociatedPullRequests(commitHashes, targetTimestamp) { pending.push({ commitHash, cursor: connection.pageInfo.endCursor }); } } - for (let index = 0; index < commitHashes.length; index += 40) { - const chunk = commitHashes.slice(index, index + 40); + for (let index = 0; index < commitHashes.length; index += commitAssociationQueryBatchSize) { + const chunk = commitHashes.slice(index, index + commitAssociationQueryBatchSize); const fields = chunk .map( (hash, offset) => diff --git a/CHANGELOG.md b/CHANGELOG.md index adf64b7f660..0c68a45198e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Docs: https://docs.openclaw.ai ### Highlights -- **Richer Telegram delivery:** Telegram now sends rich HTML, preserves rich markdown and sticker paths, renders progress drafts and command output more faithfully, and keeps mentions and spooled handlers on the right delivery path. (#93286, #93164, #93124, #93364, #93130, #93088, #93281) Thanks @obviyus, @vincentkoc, @goutamadwant, @kesslerio, @NianJiuZst, @SweetSophia, @Marvinthebored, and @aaajiao. +- **Richer Telegram delivery:** Telegram now sends rich HTML, preserves rich markdown and sticker paths, renders progress drafts and command output more faithfully, normalizes HTML tables safely, and keeps mentions and spooled handlers on the right delivery path. (#93286, #93164, #93124, #93364, #93130, #93088, #93281, #94891, #94856) Thanks @obviyus, @vincentkoc, @goutamadwant, @kesslerio, @NianJiuZst, @SweetSophia, @Marvinthebored, @aaajiao, @zhangqueping, and @jairrab. - **More dependable agent recovery:** retries, terminal outcomes, usage after compaction, session history repair, and reply reconciliation now keep more interrupted or partial turns moving toward a visible final result. (#92191, #93073, #93228, #93084, #93469, #93291, #90943) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, @yetval, @sandieman2, and @vincentkoc. - **A stronger Codex integration:** Codex gains automatic plugin approvals, GPT-5.3 Spark OAuth routing, remote-node `exec` as a dynamic tool, and more reliable app-server teardown and terminal outcomes. (#92625, #89133, #93654, #91767, #93287) Thanks @kevinslin, @VACInc, @vincentkoc, @JPKay-AI, and @aliahnaf2013-max. - **Standalone official provider plugins:** external provider packages are now first-class npm releases, externally installed channel plugins load at Gateway startup, and StepFun is available from npm and ClawHub. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc. @@ -18,22 +18,22 @@ Docs: https://docs.openclaw.ai - Providers and auth: add Codex Hosted Search, improve Gemini CLI OAuth behind proxies, and keep external provider onboarding on current choices and package metadata. (#93446, #92815) Thanks @fuller-stack-dev, @yetval, @EvetteYoung, and @vincentkoc. - Plugins and installs: externalized official providers publish as independent npm packages, Gateway discovers installed channel plugins at startup, and StepFun installs from npm or ClawHub. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc. - Dashboard and mobile: add a session workspace rail, plugin health in status, compact cron lists, and iOS Watch controls. (#92856, #91952, #93395, #93387) Thanks @Solvely-Colin, @jalehman, @yu-xin-c, @centralpc, @joshavant, and @vincentkoc. -- Codex and skills: add automatic plugin approvals, preserve ClawHub skill provenance, and expose remote-node execution to Codex when a node is connected. (#92625, #93283, #93654) Thanks @kevinslin, @momothemage, @nmccready-tars, @vincentkoc, and @JPKay-AI. -- QA and release engineering: QA scenarios now use YAML, with broader profile evidence and release coverage for the plugin and channel matrix. +- Codex, observability, and skills: add automatic plugin approvals and SecretRefs, preserve ClawHub skill provenance, add OpenTelemetry log export, and expose remote-node execution to Codex when a node is connected. (#92625, #94324, #93283, #94561, #93654) Thanks @kevinslin, @kevinlin-openai, @momothemage, @nmccready-tars, @jesse-merhi, @vincentkoc, and @JPKay-AI. +- QA and release engineering: QA scenarios now use YAML, with broader profile evidence and release coverage for the plugin and channel matrix. Thanks @vincentkoc. ### Fixes - Security and privacy: redact secrets from debug/config output, block internal HTTP session overrides, audit open-DM tool exposure, and retain plugin write ownership checks. (#93333, #88496, #93443, #92883, #93353) Thanks @Alix-007, @jason-allen-oneal, @coygeek, @RichardCao, @yu-xin-c, @cjg20ss, @eleqtrizit, and @vincentkoc. -- Agent and session runtime: retry thinking-only and empty post-tool turns, prevent duplicate hook execution, preserve fresh usage through compaction, and repair partial JSON/history artifacts. (#92191, #93073, #93009, #93084, #93469) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @zenglingbiao, @dertbv, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, and @vincentkoc. -- Channels and replies: fix Telegram rich delivery and ingress recovery, preserve WhatsApp auth and media error reporting, keep Mattermost thread replies intact, and harden Discord action handling. (#93286, #93364, #93281, #93076, #93334, #93424, #93488) Thanks @obviyus, @NianJiuZst, @mcaxtr, @rushindrasinha, @amknight, @lzyyzznl, @darealgege, and @vincentkoc. +- Agent and session runtime: retry thinking-only and empty post-tool turns, prevent duplicate hook execution, preserve pending subagent delivery, preserve fresh usage through compaction, and repair partial JSON/history artifacts. (#92191, #93073, #93009, #93084, #93469, #94349, #92383, #94257) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @zenglingbiao, @dertbv, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, @vincentkoc, @sallyom, @oiGaDio, @Hidetsugu55, and @Nas01010101. +- Channels and replies: fix Telegram rich delivery, table rendering, action-error handling, and ingress recovery; preserve command progress detail across channel adapters; retain WhatsApp opening text after a media failure; keep Mattermost thread replies intact; and harden Discord action handling. (#93286, #93364, #93281, #93076, #93334, #93424, #93488, #94868, #94891, #94856, #94810, #93823) Thanks @obviyus, @NianJiuZst, @mcaxtr, @rushindrasinha, @amknight, @lzyyzznl, @darealgege, @vincentkoc, @zhangqueping, @jairrab, @ZOOWH, @parveshsaini, and @yetval. - Storage and migrations: avoid SQLite WAL on network filesystems, clean reindex artifacts, keep setup state out of workspace dot-directories, and import default-agent auth profiles into SQLite. (#93454, #92891, #93182, #93295, #93520, #93156) Thanks @vincentkoc, @ZengWen-DT, @Zeng-wen, @potterdigital, @Alix-007, @Pick-cat, @sallyom, @1qh, and @Tazio7. - Provider and model behavior: fix Gemini CLI proxy OAuth, restore Codex Spark OAuth routing, correct Bedrock embedding model IDs, and preserve configured defaults in embedded runs. (#92815, #89133, #93452, #93428) Thanks @yetval, @EvetteYoung, @VACInc, @LiuwqGit, @aleck31, @zenglingbiao, @danielgerlag, and @vincentkoc. - CLI, TUI, and apps: accept global flags after subcommands, keep terminal output and activity indicators visible, preserve CJK IME composition, and refresh stale UI state. (#93455, #93460, #93006, #93427, #93498, #93606) Thanks @ooiuuii, @Alix-007, @ZengWen-DT, @Zeng-wen, @AlethiaQuizForge, @Zhaoqj2016, @liuhao1024, @BrianClaw1955, @vincentkoc, and @NicoBoom13. -- Operations and updates: harden official plugin recovery, restart managed Gateways after failed update handoff, avoid Node-specific npm prefixes, and keep package validation paths reliable. (#93325, #92111, #93650) Thanks @vincentkoc, @yetval, @ofan, and @yaanfpv. +- Operations and updates: harden official plugin recovery, restart managed Gateways after failed update handoff, keep safe cron delivery defaults, avoid Node-specific npm prefixes, and keep package validation paths reliable. (#93325, #92111, #93650, #94453, #91685) Thanks @vincentkoc, @yetval, @ofan, @yaanfpv, @jincheng-xydt, @sallyom, @davectr, and @nxmxbbd. ### Complete contribution record -This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact. +This audited record covers the complete v2026.6.8..HEAD history: 422 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact. #### Pull requests @@ -175,7 +175,7 @@ This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PR - **PR #90003** feat(policy): cover exec approvals artifact. Thanks @giodl73-repo. - **PR #93448** fix(guards): allow auth profile sqlite reader. Thanks @amknight. - **PR #93424** fix(mattermost): keep message tool replies in threads. Thanks @amknight and @vincentkoc. -- **PR #93418** fix(telegram): forward Bot API 10.1 rich_message content to agent. Related #93410. Thanks @xzh-xydt and @vincentkoc and @0pen7ech. +- **PR #93418** fix(telegram): forward Bot API 10.1 rich_message content to agent. Related #93410. Thanks @xzh-icenter and @vincentkoc and @0pen7ech. - **PR #93175** test(qa): taxonomy profiles: includeAllCategories for release profile, update some coverage. Thanks @RomneyDa. - **PR #93456** fix(agents): handle string assistant message content. Thanks @vincentkoc. - **PR #93441** fix(outbound): ignore schema-padded poll metadata on send. Related #43015. Thanks @weichengdeng and @charzhou. @@ -412,6 +412,53 @@ This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PR - **PR #94658** test(sqlite): use shared temp directory helper. Thanks @vincentkoc. - **PR #92135** fix(openai-embedding): preserve openai/ prefix for non-native base URLs. Related #92124. Thanks @xialonglee and @Kambrian. - **PR #93737** refactor: add session maintenance transaction seam. Thanks @jalehman. +- **PR #93685** refactor(auto-reply): add lifecycle storage seams. Thanks @jalehman. +- **PR #94349** fix(agents): preserve pending subagent completion announces. Related #93323. Thanks @sallyom and @oiGaDio. +- **PR #93174** test: fold channel message flows into qa e2e. Thanks @RomneyDa. +- **PR #94093** Prevent Codex thread rotation from losing next-step context. Thanks @VACInc. +- **PR #53920** fix(scripts): avoid mutating tracked auth-monitor template during setup. Thanks @JackWuGlobal. +- **PR #94702** Standardize QA coverage IDs on dotted names. Thanks @RomneyDa. +- **PR #81825** fix(skills/1password): stop forcing tmux for desktop app auth (#52540). Thanks @koshaji and @tylerbittner. +- **PR #94725** fix(doctor): warn on volatile SQLite state. Thanks @vincentkoc. +- **PR #88551** fix(agents): skip auth gate for CLI-owned transport. Thanks @yu-xin-c. +- **PR #88581** feat(commands): add /name to rename the current session from chat. Thanks @BSG2000. +- **PR #94324** feat(codex): support app-server SecretRefs. Thanks @kevinlin-openai and @kevinslin. +- **PR #90882** fix: add self-knowledge docs rule to system prompt. Related #90713. Thanks @SutraHsing. +- **PR #94684** fix: #80507 show dry-run output for message send/poll. Thanks @lzyyzznl and @YB0y. +- **PR #93823** fix(whatsapp): keep opening text chunk when first media fails on multi-chunk reply. Thanks @yetval. +- **PR #89203** refactor: route SDK session compatibility through seam. Thanks @jalehman. +- **PR #94453** fix: default cron runMode to "due" instead of "force" (#94270). Thanks @jincheng-xydt and @sallyom and @davectr. +- **PR #94746** fix(note): prevent clack from re-breaking copy-sensitive tokens. Related #94730. Thanks @xzh-icenter and @berkgungor. +- **PR #89904** refactor: route sdk session compatibility through accessor. Thanks @jalehman. +- **PR #86719** fix(skills): retarget stale plugin skill symlinks. Related #85925. Thanks @stevenepalmer and @shakkernerd. +- **PR #94337** fix(tui): show 0 not ? for fresh-session context tokens in footer. Thanks @mushuiyu886. +- **PR #94539** fix(android): group settings by intent. Thanks @Tosko4. +- **PR #92383** fix(gateway): never return an empty chat.history transcript. Thanks @Hidetsugu55. +- **PR #92574** test(browser): cover action-input CLI request bodies. Related #83877. Thanks @yu-xin-c and @davinci282828. +- **PR #92873** test(diffs): add viewerState, toolbar toggle, shadow root, and hydrateProps tests (fixes #83915). Thanks @liuhao1024 and @davinci282828. +- **PR #94257** fix(sessions): preserve Media\* index alignment when reading user-turn fields. Thanks @Nas01010101. +- **PR #94756** fix(codex): bound turn/start text when context budget is non-positive. Related #94748. Thanks @Nas01010101. +- **PR #94729** fix(skills/trello): add curl to requires.bins to match body examples (fixes #94727). Thanks @liuhao1024 and @berkgungor. +- **PR #94790** feat(slack): log INFO receipt for inbound app_mention events. Related #94691. Thanks @ZengWen-DT and @BryceMurray. +- **PR #81696** fix: guard tool event callbacks (AI-assisted). Thanks @enjoylife1243. +- **PR #94809** chore: forward-port alpha release fixes. +- **PR #94612** fix(macos): open NSOpenPanel for embedded Control UI file inputs (#94468). Thanks @bbblending and @DINGDANGMAOUP. +- **PR #89806** fix(feishu): avoid axios interceptor internals. Related #83913. Thanks @sweetcornna and @davinci282828. +- **PR #91923** fix(ios): clean up notification settings state. Thanks @zats. +- **PR #91345** fix: suggest close CLI commands. Related #83999. Thanks @glenn-agent and @HannesOberreiter. +- **PR #94561** Add stdout diagnostics OTEL log exporter. Thanks @jesse-merhi. +- **PR #91013** fix(gateway): ignore stale abort markers for fresh chat events. Related #91012. Thanks @nxmxbbd. +- **PR #89279** fix(tasks): deliver ACP completions to bound Discord threads. Related #84022. Thanks @anyech and @h-mascot. +- **PR #91656** test(cron): expand parseAbsoluteTimeMs test coverage to 39 cases. Related #91654. Thanks @SpecialLeon. +- **PR #94810** fix(telegram): classify sendChatAction 401 by structured error_code, not bare substring match. Related #94787. Thanks @ZOOWH and @parveshsaini. +- **PR #94737** fix(reply): clarify provider internal error copy. Thanks @snowzlmbot. +- **PR #94868** fix(channels): preserve command progress detail. Thanks @vincentkoc. +- **PR #94891** fix(telegram): send progress previews as html text. Thanks @obviyus. +- **PR #94683** fix(outbound): keep direct-only targets out of group sessions. Related #92384. Thanks @scotthuang and @haiwei01. +- **PR #92477** fix: migrate watch app to single-target app (Xcode 27+ compat). Thanks @zats and @joshavant. +- **PR #94812** test(perf): compare saved CLI startup benchmarks. Thanks @FelixIsaac. +- **PR #94856** fix(telegram): normalize all HTML tables before entity-escaping in rich messages. Related #94317. Thanks @zhangqueping and @jairrab. +- **PR #91685** fix(cron): refuse keyless implicit isolated cron delivery inherited from shared agent-main bucket. Thanks @nxmxbbd. ## 2026.6.8 diff --git a/scripts/docker/install-sh-e2e/run.sh b/scripts/docker/install-sh-e2e/run.sh index 434361d45ab..cca75749188 100755 --- a/scripts/docker/install-sh-e2e/run.sh +++ b/scripts/docker/install-sh-e2e/run.sh @@ -309,15 +309,63 @@ function extractTrailingJsonObject(input) { try { return JSON.parse(trimmed); } catch { - // Some local runs emit stderr diagnostics before the final JSON payload. - // Walk backward and keep the last parseable top-level object. - for (let index = trimmed.lastIndexOf("{"); index >= 0; index = trimmed.lastIndexOf("{", index - 1)) { - const candidate = trimmed.slice(index); + // Agent lifecycle diagnostics can surround --json output. Prefer an agent + // result envelope so a structured post-result diagnostic cannot replace it. + let lastParsed; + let lastAgentResult; + for (let index = 0; index < trimmed.length; index += 1) { + if (trimmed[index] !== "{") { + continue; + } + let depth = 0; + let inString = false; + let escaping = false; + let end = -1; + for (let cursor = index; cursor < trimmed.length; cursor += 1) { + const char = trimmed[cursor]; + if (inString) { + if (escaping) { + escaping = false; + } else if (char === "\\") { + escaping = true; + } else if (char === '"') { + inString = false; + } + continue; + } + if (char === '"') { + inString = true; + } else if (char === "{") { + depth += 1; + } else if (char === "}") { + depth -= 1; + if (depth === 0) { + end = cursor; + break; + } + } + } + if (end < 0) { + continue; + } try { - return JSON.parse(candidate); + lastParsed = JSON.parse(trimmed.slice(index, end + 1)); + if ( + Array.isArray(lastParsed?.result?.payloads) || + Array.isArray(lastParsed?.payloads) + ) { + lastAgentResult = lastParsed; + } } catch { // keep scanning } + index = end; + } + if (lastAgentResult !== undefined) { + return lastAgentResult; + } + if (lastParsed !== undefined) { + return lastParsed; } throw new Error(`could not extract JSON payload from agent output:\n${trimmed}`); } diff --git a/test/scripts/test-install-sh-docker.test.ts b/test/scripts/test-install-sh-docker.test.ts index 423f04061ff..527d8db5beb 100644 --- a/test/scripts/test-install-sh-docker.test.ts +++ b/test/scripts/test-install-sh-docker.test.ts @@ -1,7 +1,8 @@ // Test Install Sh Docker tests cover test install sh docker script behavior. import { spawn, spawnSync } from "node:child_process"; -import { existsSync, readFileSync } from "node:fs"; -import path from "node:path"; +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path, { join } from "node:path"; import { runInNewContext } from "node:vm"; import { afterEach, describe, expect, it } from "vitest"; import { createTempDirTracker } from "../helpers/temp-dir.js"; @@ -103,6 +104,36 @@ function runDefaultSmokePlatform(env: Record, hostArch: string): return result.stdout; } +function extractInstallE2eAgentJsonParser(): string { + const script = readFileSync(INSTALL_E2E_RUNNER_PATH, "utf8"); + const match = script.match( + /node - <<'NODE' "\$out_json"\n([\s\S]*?)\nNODE\n\}\n\nRUN_AGENT_TURN_BG_PID/u, + ); + if (!match) { + throw new Error("install E2E agent JSON parser was not found"); + } + return match[1]; +} + +function normalizeInstallE2eAgentOutput(output: string) { + const root = mkdtempSync(join(tmpdir(), "openclaw-install-e2e-agent-output-")); + const outputPath = join(root, "agent.json"); + writeFileSync(outputPath, output, "utf8"); + try { + const result = spawnSync(process.execPath, ["-", outputPath], { + encoding: "utf8", + input: extractInstallE2eAgentJsonParser(), + }); + return { + output: readFileSync(outputPath, "utf8"), + status: result.status, + stderr: result.stderr, + }; + } finally { + rmSync(root, { force: true, recursive: true }); + } +} + async function waitForCondition( predicate: () => boolean, label: string, @@ -329,7 +360,9 @@ describe("test-install-sh-docker", () => { expect(script).toContain('source "$ROOT_DIR/scripts/lib/host-timeout.sh"'); expect(script).toContain('DOCKER_PULL_TIMEOUT="${OPENCLAW_DOCKER_SETUP_PULL_TIMEOUT:-600s}"'); expect(script).toContain("run_docker_pull()"); - expect(script).toContain('openclaw_host_timeout_cmd "$DOCKER_PULL_TIMEOUT" docker pull "$image"'); + expect(script).toContain( + 'openclaw_host_timeout_cmd "$DOCKER_PULL_TIMEOUT" docker pull "$image"', + ); expect(timeoutHelper).toContain("elif command -v gtimeout >/dev/null 2>&1; then"); expect(timeoutHelper).toContain('"$timeout_bin" --kill-after=30s "$timeout_value" "$@"'); expect(script).toContain('run_docker_pull "$IMAGE_NAME"'); @@ -587,6 +620,22 @@ describe("install-sh E2E runner", () => { expect(script).toContain('\\"timeoutSeconds\\":${OPENAI_PROVIDER_TIMEOUT_SECONDS}'); }); + it("normalizes agent JSON when structured lifecycle diagnostics follow the result", () => { + const payload = { + result: { + payloads: [{ text: "LEFT=RED RIGHT=GREEN" }], + }, + replayInvalid: true, + }; + const result = normalizeInstallE2eAgentOutput( + `${JSON.stringify(payload, null, 2)}\n[agent] ${JSON.stringify({ stopReason: "stop" })}\n`, + ); + + expect(result.status).toBe(0); + expect(result.stderr).toBe(""); + expect(JSON.parse(result.output)).toEqual(payload); + }); + it.each([ ["turn timeout", "OPENCLAW_INSTALL_E2E_AGENT_TURN_TIMEOUT_SECONDS", "300s"], ["provider timeout", "OPENCLAW_INSTALL_E2E_OPENAI_PROVIDER_TIMEOUT_SECONDS", "1e3"],