Compare commits

..

66 Commits

Author SHA1 Message Date
quotentiroler
b4aefa3937 feat(ci): add staged release pipeline workflows (dormant)
Add all new CI/CD workflow files for the staged branch promotion pipeline
(develop  alpha  beta  main). Push triggers are intentionally disabled
workflows use workflow_dispatch or workflow_call only.

No changes to existing ci.yml. All workflows are inert until activated in
a follow-up PR.

New workflows:
- feature-pr.yml: auto-create PRs to develop (disabled)
- hotfix-pr.yml: emergency hotfix PRs to main (disabled)
- promote-branch.yml: staged promotion (disabled)
- release-orchestrator.yml: release pipeline (disabled)
- deployment-strategy.yml: npm + Docker deploy (workflow_call)
- testing-strategy.yml: progressive test gates (workflow_call)
- generate-changelog.yml: changelog generation (workflow_call)
- version-operations.yml: version bumping (workflow_call)
- release.yml: manual release trigger (workflow_dispatch)
- rollback.yml: emergency rollback (workflow_dispatch)
- discord-notify action: reusable notification

Also adds pipeline docs and updates CONTRIBUTING.md with future branch
strategy (clearly marked as not yet active).

Split from #10755 for safe, additive merge.
2026-02-07 09:50:02 -08:00
大猫子
a4d5c7f673 docs: add missing HEARTBEAT.md and MEMORY.md to bootstrap files list (#8105)
* docs: add missing HEARTBEAT.md and MEMORY.md to bootstrap files list

Fixes #7928

The documentation for skipBootstrap and workspace setup was missing
HEARTBEAT.md and MEMORY.md from the bootstrap files list.

Changes:
- docs/gateway/configuration.md: Add HEARTBEAT.md and MEMORY.md
- docs/zh-CN/gateway/configuration.md: Same for Chinese version
- docs/start/openclaw.md: Add HEARTBEAT.md, clarify MEMORY.md is optional
- docs/zh-CN/start/openclaw.md: Same for Chinese version

* fix: reference PR number instead of issue in CHANGELOG

* docs(workspace): align bootstrap file docs with runtime (#8105)

---------

Co-authored-by: damaozi <1811866786@qq.com>
Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>
2026-02-07 10:51:44 -05:00
Seb Slight
9a3f62cb86 docs: add symptom-first troubleshooting hub and deep runbooks (#11196)
* docs(troubleshooting): add symptom-first troubleshooting runbooks

* docs(troubleshooting): fix approvals command examples

* docs(troubleshooting): wrap symptom cases in accordions

* docs(automation): clarify userTimezone missing-key behavior

* docs(troubleshooting): fix first-60-seconds ladder order
2026-02-07 10:28:19 -05:00
Tyler Yust
1007d71f0c fix: comprehensive BlueBubbles and channel cleanup (#11093)
* feat(bluebubbles): auto-strip markdown from outbound messages (#7402)

* fix(security): add timeout to webhook body reading (#6762)

Adds 30-second timeout to readBody() in voice-call, bluebubbles, and nostr
webhook handlers. Prevents Slow-Loris DoS (CWE-400, CVSS 7.5).
Merged with existing maxBytes protection in voice-call.

* fix(security): unify Error objects and lint fixes in webhook timeouts (#6762)

* fix: prevent plugins from auto-enabling without user consent (#3961)

Changes default plugin enabled state from true to false in enablePluginEntry().
Preserves existing enabled:true values. Fixes #3932.

* fix: apply hierarchical mediaMaxMb config to all channels (#8749)

Generalizes resolveAttachmentMaxBytes() to use account → channel → global
config resolution for all channels, not just BlueBubbles. Fixes #7847.

* fix(bluebubbles): sanitize attachment filenames against header injection (#10333)

Strip ", \r, \n, and \\ from filenames after path.basename() to prevent
multipart Content-Disposition header injection (CWE-93, CVSS 5.4).
Also adds sanitization to setGroupIconBlueBubbles which had zero filename
sanitization.

* fix(lint): exclude extensions/ from Oxlint preflight check (#9313)

Extensions use PluginRuntime|null patterns that trigger
no-redundant-type-constituents because PluginRuntime resolves to any.
Excluding extensions/ from Oxlint unblocks user upgrades.
Re-applies the approach from closed PR #10087.

* fix(bluebubbles): add tempGuid to createNewChatWithMessage payload (#7745)

Non-Private-API mode (AppleScript) requires tempGuid in send payloads.
The main sendMessageBlueBubbles already had it, but createNewChatWithMessage
was missing it, causing 400 errors for new chat creation without Private API.

* fix: send stop-typing signal when run ends with NO_REPLY (#8785)

Adds onCleanup callback to the typing controller that fires when the
controller is cleaned up while typing was active (e.g., after NO_REPLY).
Channels using createTypingCallbacks automatically get stop-typing on
cleanup. This prevents the typing indicator from lingering in group chats
when the agent decides not to reply.

* fix(telegram): deduplicate skill commands in multi-agent setup (#5717)

Two fixes:
1. Skip duplicate workspace dirs when listing skill commands across agents.
   Multiple agents sharing the same workspace would produce duplicate commands
   with _2, _3 suffixes.
2. Clear stale commands via deleteMyCommands before registering new ones.
   Commands from deleted skills now get cleaned up on restart.

* fix: add size limits to unbounded in-memory caches (#4948)

Adds max-size caps with oldest-entry eviction to prevent OOM in
long-running deployments:
- BlueBubbles serverInfoCache: 64 entries (already has TTL)
- Google Chat authCache: 32 entries
- Matrix directRoomCache: 1024 entries
- Discord presenceCache: 5000 entries per account

* fix: address review concerns (#11093)

- Chain deleteMyCommands → setMyCommands to prevent race condition (#5717)
- Rename enablePluginEntry to registerPluginEntry (now sets enabled: false)
- Add Slow-Loris timeout test for readJsonBody (#6023)
2026-02-07 05:00:55 -08:00
Peter Steinberger
9f703a44dc chore(release): 2026.2.6-3 2026-02-07 00:44:32 -08:00
Peter Steinberger
85ed6c7fa4 chore(onboard): reorder xAI + Qianfan providers 2026-02-07 00:43:13 -08:00
Peter Steinberger
ad4dd0422e chore(release): 2026.2.6-2 2026-02-07 00:30:43 -08:00
Peter Steinberger
4ba9809f18 test(hooks): stabilize session-memory hook tests 2026-02-07 00:22:34 -08:00
Peter Steinberger
80d42eb0ba fix(docker): support .mjs entrypoints in images and e2e 2026-02-07 00:22:34 -08:00
Peter Steinberger
2b6cf03b47 fix(build): support daemon-cli .mjs bundles in compat shim 2026-02-07 00:22:34 -08:00
Peter Steinberger
88ffad1c4f Merge PR #8868: add Baidu Qianfan support (thanks @ide-rea) 2026-02-07 00:19:04 -08:00
Peter Steinberger
875324e7c7 docs(changelog): note CI pipeline optimization (#10784) (thanks @mcaxtr) 2026-02-06 23:31:48 -08:00
Marcus Castro
2d7428a7f2 ci: re-enable parallel vitest on Windows CI 2026-02-06 23:31:48 -08:00
Marcus Castro
47596257ea ci: add concurrency controls, consolidate macOS jobs, optimize Windows CI 2026-02-06 23:31:48 -08:00
Peter Steinberger
ab3045cb48 chore(onboard): move xAI below Google 2026-02-06 23:06:55 -08:00
Peter Steinberger
aaddbdae52 chore(release): 2026.2.6-1 2026-02-06 22:48:19 -08:00
Peter Steinberger
8d0e7997c8 chore(onboard): move xAI up in auth list 2026-02-06 22:41:19 -08:00
Peter Steinberger
31a7e4f937 chore(skills): remove bird skill 2026-02-06 22:28:44 -08:00
Peter Steinberger
c5194d8148 fix(dashboard): restore tokenized control ui links 2026-02-06 22:17:09 -08:00
ide-rea
43c0a7fe1c Merge branch 'openclaw:main' into qianfan 2026-02-07 14:07:52 +08:00
Jake
e78ae48e69 fix(memory): add input_type to Voyage AI embeddings for improved retrieval (#10818)
* fix(memory): add input_type to Voyage AI embeddings for improved retrieval

Voyage AI recommends passing input_type='document' when indexing and
input_type='query' when searching. This improves retrieval quality by
optimising the embedding space for each direction.

Changes:
- embedQuery now passes input_type: 'query'
- embedBatch now passes input_type: 'document'
- Batch API request_params includes input_type: 'document'
- Tests updated to verify input_type is passed correctly

* Changelog: note Voyage embeddings input_type fix (#10818) (thanks @mcinteerj)

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-06 21:55:09 -06:00
Markus Buhatem Koch
4c1da23a71 Revert previous change from 'Clawdbot' to 'OpenClaw' in lore (#9119) 2026-02-06 21:53:02 -05:00
Val Alexander
3d2fe9284e Fix repository links in formal-verification.md (#10200)
Updated repository links for formal verification models.
2026-02-06 21:47:55 -05:00
Peter Steinberger
a3b5f1b15c fix(build): unblock pnpm build dts 2026-02-06 18:43:11 -08:00
DEOKLYONG MOON
d1dc60774b Docs: fix broken /plugins links (#9308)
* Docs: fix broken /plugins links to /plugin

The documentation linked to /plugins which doesn't exist.
The correct path is /plugin (singular) which contains the
plugins overview documentation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: drop manual zh-CN doc edits from plugins link fix

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>
2026-02-06 21:08:26 -05:00
Tyler Yust
d90cac990c fix: cron scheduler reliability, store hardening, and UX improvements (#10776)
* refactor: update cron job wake mode and run mode handling

- Changed default wake mode from 'next-heartbeat' to 'now' in CronJobEditor and related CLI commands.
- Updated cron-tool tests to reflect changes in run mode, introducing 'due' and 'force' options.
- Enhanced cron-tool logic to handle new run modes and ensure compatibility with existing job structures.
- Added new tests for delivery plan consistency and job execution behavior under various conditions.
- Improved normalization functions to handle wake mode and session target casing.

This refactor aims to streamline cron job configurations and enhance the overall user experience with clearer defaults and improved functionality.

* test: enhance cron job functionality and UI

- Added tests to ensure the isolated agent correctly announces the final payload text when delivering messages via Telegram.
- Implemented a new function to pick the last deliverable payload from a list of delivery payloads.
- Enhanced the cron service to maintain legacy "every" jobs while minute cron jobs recompute schedules.
- Updated the cron store migration tests to verify the addition of anchorMs to legacy every schedules.
- Improved the UI for displaying cron job details, including job state and delivery information, with new styles and layout adjustments.

These changes aim to improve the reliability and user experience of the cron job system.

* test: enhance sessions thinking level handling

- Added tests to verify that the correct thinking levels are applied during session spawning.
- Updated the sessions-spawn-tool to include a new parameter for overriding thinking levels.
- Enhanced the UI to support additional thinking levels, including "xhigh" and "full", and improved the handling of current options in dropdowns.

These changes aim to improve the flexibility and accuracy of thinking level configurations in session management.

* feat: enhance session management and cron job functionality

- Introduced passthrough arguments in the test-parallel script to allow for flexible command-line options.
- Updated session handling to hide cron run alias session keys from the sessions list, improving clarity.
- Enhanced the cron service to accurately record job start times and durations, ensuring better tracking of job execution.
- Added tests to verify the correct behavior of the cron service under various conditions, including zero-delay timers.

These changes aim to improve the usability and reliability of session and cron job management.

* feat: implement job running state checks in cron service

- Added functionality to prevent manual job runs if a job is already in progress, enhancing job management.
- Updated the `isJobDue` function to include checks for running jobs, ensuring accurate scheduling.
- Enhanced the `run` function to return a specific reason when a job is already running.
- Introduced a new test case to verify the behavior of forced manual runs during active job execution.

These changes aim to improve the reliability and clarity of cron job execution and management.

* feat: add session ID and key to CronRunLogEntry model

- Introduced `sessionid` and `sessionkey` properties to the `CronRunLogEntry` struct for enhanced tracking of session-related information.
- Updated the initializer and Codable conformance to accommodate the new properties, ensuring proper serialization and deserialization.

These changes aim to improve the granularity of logging and session management within the cron job system.

* fix: improve session display name resolution

- Updated the `resolveSessionDisplayName` function to ensure that both label and displayName are trimmed and default to an empty string if not present.
- Enhanced the logic to prevent returning the key if it matches the label or displayName, improving clarity in session naming.

These changes aim to enhance the accuracy and usability of session display names in the UI.

* perf: skip cron store persist when idle timer tick produces no changes

recomputeNextRuns now returns a boolean indicating whether any job
state was mutated. The idle path in onTimer only persists when the
return value is true, eliminating unnecessary file writes every 60s
for far-future or idle schedules.

* fix: prep for merge - explicit delivery mode migration, docs + changelog (#10776) (thanks @tyler6204)
2026-02-06 18:03:03 -08:00
Peter Steinberger
0dd7033521 chore(lockfile): fix pnpm-lock 2026-02-06 17:56:22 -08:00
Raymond Berger
c80a09fc2f Fix QMD CLI installation link in memory.md (#8647)
Correct the installation link for the QMD CLI in the documentation.
2026-02-06 20:53:47 -05:00
Peter Steinberger
f831c48e56 docs(changelog): highlight Opus 4.6 + Codex 5.3 first 2026-02-06 17:28:46 -08:00
Peter Steinberger
c237a82b42 docs(changelog): curate 2026.2.6 2026-02-06 16:56:29 -08:00
Peter Steinberger
94b2fc14f2 chore(deps): bump carbon beta 2026-02-06 16:56:25 -08:00
Peter Steinberger
0daf416908 fix(agents): add Opus 4.6 forward-compat fallback 2026-02-06 16:56:21 -08:00
Peter Steinberger
dca8cf958b chore(deps): update deps 2026-02-06 16:37:56 -08:00
Seb Slight
93bf75279f docs(imessage): improve macOS TCC troubleshooting guidance (#10781) 2026-02-06 19:21:52 -05:00
gitpds
fe308a3aa1 docs(imessage): add macOS TCC troubleshooting 2026-02-06 19:10:01 -05:00
Peter Steinberger
7be921c434 docs(changelog): refresh 2026.2.6 since v2026.2.3 2026-02-06 15:47:10 -08:00
Peter Steinberger
5163833be5 docs: fix markdownlint fragments + headings 2026-02-06 15:45:39 -08:00
Peter Steinberger
d898ad6807 fix(telegram): cast fetch for grammY ApiClientOptions 2026-02-06 15:45:34 -08:00
Peter Steinberger
677450cd9b chore(release): bump version to 2026.2.6 2026-02-06 15:37:31 -08:00
Peter Steinberger
ac5944cde7 docs(changelog): include merged PRs since v2026.2.3 2026-02-06 15:36:13 -08:00
Peter Steinberger
3b768a2851 docs(changelog): prepare 2026.2.6 2026-02-06 15:30:17 -08:00
Shadril Hassan Shifat
2c8af78d20 fix(hooks): replace debug console.log with proper subsystem logging in session-memory (#10730)
* fix: replace debug console.log with proper subsystem logging in session-memory

* fix(hooks): normalize session-memory subsystem logging

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-06 17:22:38 -06:00
calvin-hpnet
48b0fd8d88 feat(antigravity): update default model to Claude Opus 4.6 (#10720)
* feat(antigravity): update default model to Claude Opus 4.6

Claude Opus 4.5 has been replaced by Claude Opus 4.6 on the
Antigravity (Google Cloud Code Assist) platform.

- Update DEFAULT_MODEL in google-antigravity-auth extension
- Update testing docs to reference the new model

* fix: update remaining antigravity opus 4.5 refs in zh-CN docs and tests

Address review comments from Greptile:
- Update zh-CN/testing.md antigravity model references
- Update pi-tools-agent-config.test.ts model IDs

* Antigravity: default OAuth model to Opus 4.6 (#10720) (thanks @calvin-hpnet)

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-02-06 16:42:57 -06:00
Tak Hoffman
40425db0f1 feat(memory): document Voyage embeddings + VOYAGE_API_KEY (#7078) (thanks @mcinteerj) (#10699) 2026-02-06 15:51:47 -06:00
Jake
6965a2cc9d feat(memory): native Voyage AI support (#7078)
* feat(memory): add native Voyage AI embedding support with batching

Cherry-picked from PR #2519, resolved conflict in memory-search.ts
(hasRemote -> hasRemoteConfig rename + added voyage provider)

* fix(memory): optimize voyage batch memory usage with streaming and deduplicate code

Cherry-picked from PR #2519. Fixed lint error: changed this.runWithConcurrency
to use imported runWithConcurrency function after extraction to internal.ts
2026-02-06 15:09:32 -06:00
Tak Hoffman
e3d3893d5d Docs: revise PR and issue submission guides (#10617)
* Docs: revise PR submission guide

* Docs: revise issue submission guide
2026-02-06 13:29:11 -06:00
Yida-Dev
4216449405 fix: guard resolveUserPath against undefined input (#10176)
* fix: guard resolveUserPath against undefined input

When subagent spawner omits workspaceDir, resolveUserPath receives
undefined and crashes on .trim().  Add a falsy guard that falls back
to process.cwd(), matching the behavior callers already expect.

Closes #10089

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden runner workspace fallback (#10176) (thanks @Yida-Dev)

* fix: harden workspace fallback scoping (#10176) (thanks @Yida-Dev)

* refactor: centralize workspace fallback classification and redaction (#10176) (thanks @Yida-Dev)

* test: remove explicit any from utils mock (#10176) (thanks @Yida-Dev)

* security: reject malformed agent session keys for workspace resolution (#10176) (thanks @Yida-Dev)

---------

Co-authored-by: Yida-Dev <reyifeijun@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
2026-02-06 13:16:58 -05:00
Tak Hoffman
5842bcaaf7 Docs: add PR sign-off template (#10561) 2026-02-06 11:58:39 -06:00
ideoutrea
0b51f0d762 Fix conflicts 2026-02-06 18:16:20 +08:00
ideoutrea
7a9deb2400 Resolve conflicts 2026-02-06 18:13:42 +08:00
ide-rea
3997316fb0 Merge branch 'main' into qianfan 2026-02-06 17:58:28 +08:00
ideoutrea
360851366f Support ERNIE-5.0-Thinking-Preview 2026-02-06 17:49:54 +08:00
ideoutrea
7af00f040a Optimize import 2026-02-05 14:53:38 +08:00
ideoutrea
4d30f97407 Fix key resolve 2026-02-05 14:40:56 +08:00
ideoutrea
ff948a6dd7 Optimize doc 2026-02-05 14:04:23 +08:00
ideoutrea
ad759c9446 Optimize format 2026-02-05 13:50:09 +08:00
ide-rea
9ccbd57016 Merge branch 'openclaw:main' into qianfan 2026-02-05 13:36:44 +08:00
ideoutrea
52c9d3480f Add auth choice 2026-02-05 13:35:35 +08:00
ide-rea
517a8eafe5 Merge branch 'openclaw:main' into qianfan 2026-02-05 12:43:21 +08:00
ideoutrea
c8e67ad5d5 Fix import error 2026-02-05 12:39:38 +08:00
ideoutrea
fb5280e1b5 optimize doc 2026-02-04 22:57:34 +08:00
ide-rea
009abd306a Merge branch 'main' into qianfan 2026-02-04 22:39:13 +08:00
ideoutrea
8c53dfb74f Optimize doc 2026-02-04 22:30:42 +08:00
ideoutrea
7bf4080608 Fix format 2026-02-04 22:27:51 +08:00
ideoutrea
1de05ad068 Add baidu qianfan model provider 2026-02-04 22:27:49 +08:00
ideoutrea
30ac80b96b Add baidu qianfan model provider 2026-02-04 16:36:37 +08:00
261 changed files with 9609 additions and 2532 deletions

View File

@@ -0,0 +1,69 @@
name: Discord Notify
description: Send notifications to Discord webhook
inputs:
webhook_url:
description: Discord webhook URL
required: true
title:
description: Notification title
required: true
description:
description: Notification description
required: true
color:
description: Embed color (decimal)
required: false
default: "3447003"
username:
description: Bot username
required: false
default: "OpenClaw CI"
avatar_url:
description: Bot avatar URL
required: false
default: "https://avatars.githubusercontent.com/u/182880377"
timestamp:
description: Include timestamp
required: false
default: "true"
fields:
description: JSON array of embed fields
required: false
default: "[]"
runs:
using: composite
steps:
- name: Send Discord notification
shell: bash
run: |
TIMESTAMP=""
if [ "${{ inputs.timestamp }}" = "true" ]; then
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
fi
# Build JSON payload with jq to handle escaping properly
PAYLOAD=$(jq -n \
--arg username "${{ inputs.username }}" \
--arg avatar_url "${{ inputs.avatar_url }}" \
--arg title "${{ inputs.title }}" \
--arg description "${{ inputs.description }}" \
--argjson color "${{ inputs.color }}" \
--argjson fields '${{ inputs.fields }}' \
--arg timestamp "$TIMESTAMP" \
--argjson add_timestamp "${{ inputs.timestamp == 'true' }}" \
'{
username: $username,
avatar_url: $avatar_url,
embeds: [{
title: $title,
description: $description,
color: $color,
fields: $fields
} + (if $add_timestamp then {timestamp: $timestamp} else {} end)]
}')
curl -sS -H "Content-Type: application/json" \
-d "$PAYLOAD" \
"${{ inputs.webhook_url }}"

View File

@@ -2,8 +2,13 @@ name: CI
on:
push:
branches: [main]
pull_request:
concurrency:
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
install-check:
runs-on: blacksmith-4vcpu-ubuntu-2404
@@ -185,7 +190,9 @@ jobs:
runs-on: blacksmith-4vcpu-windows-2025
env:
NODE_OPTIONS: --max-old-space-size=4096
CLAWDBOT_TEST_WORKERS: 1
# Keep total concurrency predictable on the 4 vCPU runner:
# `scripts/test-parallel.mjs` runs some vitest suites in parallel processes.
OPENCLAW_TEST_WORKERS: 2
defaults:
run:
shell: bash
@@ -208,6 +215,25 @@ jobs:
with:
submodules: false
- name: Try to exclude workspace from Windows Defender (best-effort)
shell: pwsh
run: |
$cmd = Get-Command Add-MpPreference -ErrorAction SilentlyContinue
if (-not $cmd) {
Write-Host "Add-MpPreference not available, skipping Defender exclusions."
exit 0
}
try {
# Defender sometimes intercepts process spawning (vitest workers). If this fails
# (eg hardened images), keep going and rely on worker limiting above.
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction Stop
Add-MpPreference -ExclusionProcess "node.exe" -ErrorAction Stop
Write-Host "Defender exclusions applied."
} catch {
Write-Warning "Failed to apply Defender exclusions, continuing. $($_.Exception.Message)"
}
- name: Checkout submodules (retry)
run: |
set -euo pipefail
@@ -269,15 +295,13 @@ jobs:
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
run: ${{ matrix.command }}
checks-macos:
# Consolidated macOS job: runs TS tests + Swift lint/build/test sequentially
# on a single runner. GitHub limits macOS concurrent jobs to 5 per org;
# running 4 separate jobs per PR (as before) starved the queue. One job
# per PR allows 5 PRs to run macOS checks simultaneously.
macos:
if: github.event_name == 'pull_request'
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
include:
- task: test
command: pnpm test
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -297,6 +321,7 @@ jobs:
done
exit 1
# --- Node/pnpm setup (for TS tests) ---
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -336,71 +361,20 @@ jobs:
pnpm -v
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
- name: Run ${{ matrix.task }}
# --- Run all checks sequentially (fast gates first) ---
- name: TS tests (macOS)
env:
NODE_OPTIONS: --max-old-space-size=4096
run: ${{ matrix.command }}
macos-app:
if: github.event_name == 'pull_request'
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
include:
- task: lint
command: |
swiftlint --config .swiftlint.yml
swiftformat --lint apps/macos/Sources --config .swiftformat
- task: build
command: |
set -euo pipefail
for attempt in 1 2 3; do
if swift build --package-path apps/macos --configuration release; then
exit 0
fi
echo "swift build failed (attempt $attempt/3). Retrying…"
sleep $((attempt * 20))
done
exit 1
- task: test
command: |
set -euo pipefail
for attempt in 1 2 3; do
if swift test --package-path apps/macos --parallel --enable-code-coverage --show-codecov-path; then
exit 0
fi
echo "swift test failed (attempt $attempt/3). Retrying…"
sleep $((attempt * 20))
done
exit 1
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: false
- name: Checkout submodules (retry)
run: |
set -euo pipefail
git submodule sync --recursive
for attempt in 1 2 3 4 5; do
if git -c protocol.version=2 submodule update --init --force --depth=1 --recursive; then
exit 0
fi
echo "Submodule update failed (attempt $attempt/5). Retrying…"
sleep $((attempt * 10))
done
exit 1
run: pnpm test
# --- Xcode/Swift setup ---
- name: Select Xcode 26.1
run: |
sudo xcode-select -s /Applications/Xcode_26.1.app
xcodebuild -version
- name: Install XcodeGen / SwiftLint / SwiftFormat
run: |
brew install xcodegen swiftlint swiftformat
run: brew install xcodegen swiftlint swiftformat
- name: Show toolchain
run: |
@@ -408,8 +382,35 @@ jobs:
xcodebuild -version
swift --version
- name: Run ${{ matrix.task }}
run: ${{ matrix.command }}
- name: Swift lint
run: |
swiftlint --config .swiftlint.yml
swiftformat --lint apps/macos/Sources --config .swiftformat
- name: Swift build (release)
run: |
set -euo pipefail
for attempt in 1 2 3; do
if swift build --package-path apps/macos --configuration release; then
exit 0
fi
echo "swift build failed (attempt $attempt/3). Retrying…"
sleep $((attempt * 20))
done
exit 1
- name: Swift test
run: |
set -euo pipefail
for attempt in 1 2 3; do
if swift test --package-path apps/macos --parallel --enable-code-coverage --show-codecov-path; then
exit 0
fi
echo "swift test failed (attempt $attempt/3). Retrying…"
sleep $((attempt * 20))
done
exit 1
ios:
if: false # ignore iOS in CI for now
runs-on: macos-latest

View File

@@ -0,0 +1,349 @@
name: Deployment Strategy
# Reusable deployment workflow for staged releases
#
# Deployment targets by stage:
# - alpha: npm @alpha tag only
# - beta: npm @beta tag + Docker (ghcr.io) beta tag
# - stable: npm @latest + Docker latest + multi-arch manifest
on:
workflow_call:
inputs:
deployment_stage:
description: "Deployment stage: alpha, beta, or stable"
required: true
type: string
app_version:
description: "Version of the application to deploy"
required: true
type: string
source_branch:
description: "Source branch for deployment"
required: true
type: string
outputs:
deployment_status:
description: "Status of the deployment"
value: ${{ jobs.deploy-summary.outputs.status }}
npm_url:
description: "npm package URL"
value: ${{ jobs.deploy-summary.outputs.npm_url }}
docker_url:
description: "Docker image URL"
value: ${{ jobs.deploy-summary.outputs.docker_url }}
secrets:
NPM_TOKEN:
required: false
DISCORD_WEBHOOK_URL:
required: false
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# npm publish (all stages)
npm-publish:
name: npm Publish (${{ inputs.deployment_stage }})
runs-on: blacksmith-4vcpu-ubuntu-2404
outputs:
status: ${{ steps.publish.outputs.status }}
npm_url: ${{ steps.publish.outputs.npm_url }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.source_branch }}
submodules: false
- name: Checkout submodules (retry)
run: |
set -euo pipefail
git submodule sync --recursive
for attempt in 1 2 3 4 5; do
if git -c protocol.version=2 submodule update --init --force --depth=1 --recursive; then
exit 0
fi
echo "Submodule update failed (attempt $attempt/5). Retrying…"
sleep $((attempt * 10))
done
exit 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22.x
registry-url: "https://registry.npmjs.org"
- name: Setup pnpm (corepack retry)
run: |
set -euo pipefail
corepack enable
for attempt in 1 2 3; do
if corepack prepare pnpm@10.23.0 --activate; then
pnpm -v
exit 0
fi
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
sleep $((attempt * 10))
done
exit 1
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
- name: Build
run: pnpm build
- name: Determine npm tag
id: npm-tag
run: |
case "${{ inputs.deployment_stage }}" in
alpha)
echo "tag=alpha" >> $GITHUB_OUTPUT
;;
beta)
echo "tag=beta" >> $GITHUB_OUTPUT
;;
stable)
echo "tag=latest" >> $GITHUB_OUTPUT
;;
esac
- name: Publish to npm
id: publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "NPM_TOKEN not set, skipping publish"
echo "status=skipped" >> $GITHUB_OUTPUT
echo "npm_url=" >> $GITHUB_OUTPUT
exit 0
fi
NPM_TAG="${{ steps.npm-tag.outputs.tag }}"
if npm publish --tag "$NPM_TAG" --access public; then
echo "status=success" >> $GITHUB_OUTPUT
echo "npm_url=https://www.npmjs.com/package/openclaw/v/${{ inputs.app_version }}" >> $GITHUB_OUTPUT
else
echo "status=failed" >> $GITHUB_OUTPUT
echo "npm_url=" >> $GITHUB_OUTPUT
exit 1
fi
# Docker build - amd64 (beta+ only)
docker-amd64:
name: Docker amd64 (${{ inputs.deployment_stage }})
if: inputs.deployment_stage != 'alpha'
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.source_branch }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ inputs.app_version }}-amd64
type=raw,value=${{ inputs.deployment_stage }}-amd64
- name: Build and push amd64
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false
push: true
# Docker build - arm64 (beta+ only)
docker-arm64:
name: Docker arm64 (${{ inputs.deployment_stage }})
if: inputs.deployment_stage != 'alpha'
runs-on: ubuntu-24.04-arm
permissions:
packages: write
contents: read
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.source_branch }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ inputs.app_version }}-arm64
type=raw,value=${{ inputs.deployment_stage }}-arm64
- name: Build and push arm64
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/arm64
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false
push: true
# Create multi-arch manifest (beta+ only)
docker-manifest:
name: Docker Manifest (${{ inputs.deployment_stage }})
if: inputs.deployment_stage != 'alpha'
runs-on: ubuntu-latest
needs: [docker-amd64, docker-arm64]
permissions:
packages: write
contents: read
outputs:
docker_url: ${{ steps.manifest.outputs.docker_url }}
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest
id: manifest
run: |
STAGE="${{ inputs.deployment_stage }}"
VERSION="${{ inputs.app_version }}"
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
# Create version manifest
docker buildx imagetools create \
-t "${IMAGE}:${VERSION}" \
"${IMAGE}:${VERSION}-amd64" \
"${IMAGE}:${VERSION}-arm64"
# Create stage manifest (beta or latest)
if [ "$STAGE" = "stable" ]; then
docker buildx imagetools create \
-t "${IMAGE}:latest" \
"${IMAGE}:${VERSION}-amd64" \
"${IMAGE}:${VERSION}-arm64"
echo "docker_url=${IMAGE}:latest" >> $GITHUB_OUTPUT
else
docker buildx imagetools create \
-t "${IMAGE}:${STAGE}" \
"${IMAGE}:${VERSION}-amd64" \
"${IMAGE}:${VERSION}-arm64"
echo "docker_url=${IMAGE}:${STAGE}" >> $GITHUB_OUTPUT
fi
# Deployment summary
deploy-summary:
name: Deployment Summary
runs-on: ubuntu-latest
needs: [npm-publish, docker-manifest]
if: "!cancelled()"
outputs:
status: ${{ steps.summary.outputs.status }}
npm_url: ${{ steps.summary.outputs.npm_url }}
docker_url: ${{ steps.summary.outputs.docker_url }}
steps:
- name: Summarize deployment
id: summary
run: |
NPM_STATUS="${{ needs.npm-publish.outputs.status || 'skipped' }}"
NPM_URL="${{ needs.npm-publish.outputs.npm_url }}"
DOCKER_URL="${{ needs.docker-manifest.outputs.docker_url || '' }}"
echo "npm_url=$NPM_URL" >> $GITHUB_OUTPUT
echo "docker_url=$DOCKER_URL" >> $GITHUB_OUTPUT
if [ "$NPM_STATUS" = "success" ] || [ "$NPM_STATUS" = "skipped" ]; then
echo "status=success" >> $GITHUB_OUTPUT
else
echo "status=failed" >> $GITHUB_OUTPUT
fi
# Generate summary
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Stage | ${{ inputs.deployment_stage }} |" >> $GITHUB_STEP_SUMMARY
echo "| Version | ${{ inputs.app_version }} |" >> $GITHUB_STEP_SUMMARY
echo "| npm | $NPM_STATUS |" >> $GITHUB_STEP_SUMMARY
echo "| Docker | ${{ needs.docker-manifest.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
# Discord notification
notify:
name: Discord Notification
needs: deploy-summary
if: "!cancelled()"
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord success notification
if: ${{ env.DISCORD_WEBHOOK_URL != '' && needs.deploy-summary.outputs.status == 'success' }}
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "🚀 Deployed: ${{ inputs.deployment_stage }} v${{ inputs.app_version }}"
description: |
**npm**: ${{ needs.deploy-summary.outputs.npm_url || 'skipped' }}
**Docker**: ${{ needs.deploy-summary.outputs.docker_url || 'skipped' }}
color: "3066993"
- name: Discord failure notification
if: ${{ env.DISCORD_WEBHOOK_URL != '' && needs.deploy-summary.outputs.status != 'success' }}
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "❌ Deployment Failed: ${{ inputs.deployment_stage }}"
description: |
**Version**: ${{ inputs.app_version }}
[View Logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
color: "15158332"

97
.github/workflows/feature-pr.yml vendored Normal file
View File

@@ -0,0 +1,97 @@
name: Feature PR
# Auto-create PR from dev/* branches to develop
# This is the entry point for new features into the staging pipeline
# NOTE: push triggers disabled until staging pipeline is activated.
# To enable: uncomment the push block and remove workflow_dispatch.
# push:
# branches:
# - "dev/**"
# - "feature/**"
# - "fix/**"
on:
workflow_dispatch:
concurrency:
group: feature-pr-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
create-pr:
name: Create PR to develop
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Ensure develop branch exists
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if git ls-remote --heads origin develop | grep -q develop; then
echo "develop branch already exists"
else
echo "develop branch does not exist — creating from main"
git push origin origin/main:refs/heads/develop
fi
- name: Check for existing PR
id: check-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH="${{ github.ref_name }}"
TARGET="develop"
# Check if PR already exists
EXISTING=$(gh pr list --head "$BRANCH" --base "$TARGET" --json number --jq '.[0].number // empty')
if [ -n "$EXISTING" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "pr_number=$EXISTING" >> $GITHUB_OUTPUT
echo "PR #$EXISTING already exists for $BRANCH → $TARGET"
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Create PR
if: steps.check-pr.outputs.exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH="${{ github.ref_name }}"
TARGET="develop"
# Extract title from branch name (dev/foo-bar → foo bar)
TITLE=$(echo "$BRANCH" | sed 's|^dev/||; s|^feature/||; s|^fix/||; s|-| |g; s|_| |g')
# Capitalize first letter
TITLE="$(echo "${TITLE:0:1}" | tr '[:lower:]' '[:upper:]')${TITLE:1}"
# Create PR body
BODY=$(cat << 'PRBODY'
Auto-created PR from feature branch.
## Changes
<!-- Describe your changes here -->
---
*This PR was auto-created by the feature-pr workflow.*
PRBODY
)
gh pr create \
--base "$TARGET" \
--head "$BRANCH" \
--title "$TITLE" \
--body "$BODY"
echo "Created PR: $BRANCH → $TARGET"

View File

@@ -3,6 +3,10 @@ name: Formal models (informational conformance)
on:
pull_request:
concurrency:
group: formal-conformance-${{ github.event.pull_request.number || github.ref_name }}
cancel-in-progress: true
jobs:
formal_conformance:
runs-on: ubuntu-latest

129
.github/workflows/generate-changelog.yml vendored Normal file
View File

@@ -0,0 +1,129 @@
name: Generate Changelog
on:
workflow_call:
inputs:
version:
description: "Version for the changelog"
required: true
type: string
release_type:
description: "Release type: alpha, beta, or stable"
required: true
type: string
outputs:
changelog:
description: "Generated changelog content"
value: ${{ jobs.generate.outputs.changelog }}
changelog_file:
description: "Path to changelog file"
value: ${{ jobs.generate.outputs.changelog_file }}
jobs:
generate:
name: Generate Changelog
runs-on: ubuntu-latest
outputs:
changelog: ${{ steps.generate.outputs.changelog }}
changelog_file: ${{ steps.generate.outputs.changelog_file }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: generate
run: |
VERSION="${{ inputs.version }}"
RELEASE_TYPE="${{ inputs.release_type }}"
DATE=$(date +%Y-%m-%d)
# Start building changelog
CHANGELOG="## v${VERSION} (${DATE})\n\n"
# Initialize sections
FEATURES=""
FIXES=""
DOCS=""
CHORES=""
OTHER=""
# Get commits since last tag
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LATEST_TAG" ]; then
COMMITS=$(git log ${LATEST_TAG}..HEAD --oneline --format="%s")
else
COMMITS=$(git log --oneline --format="%s" | head -50)
fi
# Categorize commits by conventional commit type
while IFS= read -r commit; do
if [ -z "$commit" ]; then
continue
fi
# Extract type from conventional commit
if [[ "$commit" =~ ^feat(\(.+\))?:\ (.+)$ ]]; then
FEATURES="${FEATURES}- ${BASH_REMATCH[2]}\n"
elif [[ "$commit" =~ ^fix(\(.+\))?:\ (.+)$ ]]; then
FIXES="${FIXES}- ${BASH_REMATCH[2]}\n"
elif [[ "$commit" =~ ^docs(\(.+\))?:\ (.+)$ ]]; then
DOCS="${DOCS}- ${BASH_REMATCH[2]}\n"
elif [[ "$commit" =~ ^chore(\(.+\))?:\ (.+)$ ]]; then
CHORES="${CHORES}- ${BASH_REMATCH[2]}\n"
elif [[ "$commit" =~ ^refactor(\(.+\))?:\ (.+)$ ]]; then
CHORES="${CHORES}- ${BASH_REMATCH[2]}\n"
elif [[ "$commit" =~ ^test(\(.+\))?:\ (.+)$ ]]; then
CHORES="${CHORES}- ${BASH_REMATCH[2]}\n"
elif [[ "$commit" =~ ^ci(\(.+\))?:\ (.+)$ ]]; then
CHORES="${CHORES}- ${BASH_REMATCH[2]}\n"
else
# Non-conventional commit, add to other
OTHER="${OTHER}- ${commit}\n"
fi
done <<< "$COMMITS"
# Build final changelog
if [ -n "$FEATURES" ]; then
CHANGELOG="${CHANGELOG}### ✨ Features\n\n${FEATURES}\n"
fi
if [ -n "$FIXES" ]; then
CHANGELOG="${CHANGELOG}### 🐛 Bug Fixes\n\n${FIXES}\n"
fi
if [ -n "$DOCS" ]; then
CHANGELOG="${CHANGELOG}### 📚 Documentation\n\n${DOCS}\n"
fi
if [ -n "$CHORES" ]; then
CHANGELOG="${CHANGELOG}### 🔧 Maintenance\n\n${CHORES}\n"
fi
if [ -n "$OTHER" ]; then
CHANGELOG="${CHANGELOG}### Other Changes\n\n${OTHER}\n"
fi
# If no categorized commits, add a simple message
if [ -z "$FEATURES" ] && [ -z "$FIXES" ] && [ -z "$DOCS" ] && [ -z "$CHORES" ] && [ -z "$OTHER" ]; then
CHANGELOG="${CHANGELOG}No notable changes in this release.\n"
fi
# Add release metadata
CHANGELOG="${CHANGELOG}\n---\n\n"
CHANGELOG="${CHANGELOG}**Release Type**: ${RELEASE_TYPE}\n"
CHANGELOG="${CHANGELOG}**Full Changelog**: https://github.com/${{ github.repository }}/compare/${LATEST_TAG:-initial}...v${VERSION}\n"
# Escape for multiline output (random delimiter prevents collision with commit messages)
DELIMITER="CHANGELOG_$(openssl rand -hex 16)"
echo "changelog<<${DELIMITER}" >> $GITHUB_OUTPUT
echo -e "$CHANGELOG" >> $GITHUB_OUTPUT
echo "${DELIMITER}" >> $GITHUB_OUTPUT
echo "changelog_file=CHANGELOG.md" >> $GITHUB_OUTPUT
# Also write to step summary
echo "## Generated Changelog" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo -e "$CHANGELOG" >> $GITHUB_STEP_SUMMARY

96
.github/workflows/hotfix-pr.yml vendored Normal file
View File

@@ -0,0 +1,96 @@
name: Hotfix PR
# Emergency hotfix workflow - bypasses staging pipeline
# Use for critical security fixes or production-breaking bugs only
#
# Flow: hotfix/* → main (directly, with expedited review)
# NOTE: push triggers disabled until staging pipeline is activated.
# To enable: uncomment the push block and remove workflow_dispatch.
# push:
# branches:
# - "hotfix/**"
on:
workflow_dispatch:
concurrency:
group: hotfix-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
create-pr:
name: Create Hotfix PR
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for existing PR
id: check-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH="${{ github.ref_name }}"
EXISTING=$(gh pr list --head "$BRANCH" --base main --json number --jq '.[0].number // empty')
if [ -n "$EXISTING" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "pr_number=$EXISTING" >> $GITHUB_OUTPUT
echo "Hotfix PR #$EXISTING already exists"
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Create Hotfix PR
if: steps.check-pr.outputs.exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BRANCH="${{ github.ref_name }}"
# Extract title from branch name
TITLE=$(echo "$BRANCH" | sed 's|^hotfix/||; s|-| |g; s|_| |g')
TITLE="🚨 HOTFIX: $(echo "${TITLE:0:1}" | tr '[:lower:]' '[:upper:]')${TITLE:1}"
# Create PR body
BODY=$(cat << 'PRBODY'
## 🚨 Emergency Hotfix
**This PR bypasses the normal staging pipeline.**
### What's broken?
<!-- Describe the production issue -->
### Root cause
<!-- Brief explanation of what went wrong -->
### Fix
<!-- What this hotfix does -->
### Verification
- [ ] Tested locally
- [ ] Reviewed by at least one other maintainer
- [ ] Post-merge monitoring plan in place
---
⚠️ **After merging:** Cherry-pick this fix to `develop`, `alpha`, and `beta` branches to keep them in sync.
*This PR was auto-created by the hotfix-pr workflow.*
PRBODY
)
gh pr create \
--base main \
--head "$BRANCH" \
--title "$TITLE" \
--label "hotfix,priority:critical" \
--body "$BODY"
echo "Created hotfix PR: $BRANCH → main"

View File

@@ -6,6 +6,10 @@ on:
pull_request:
workflow_dispatch:
concurrency:
group: install-smoke-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
install-smoke:
runs-on: ubuntu-latest

321
.github/workflows/promote-branch.yml vendored Normal file
View File

@@ -0,0 +1,321 @@
name: Promote Branch
# Staged branch promotion for openclaw:
#
# develop → alpha → beta → main
#
# - External contributors: target `develop`
# - develop → alpha: auto-creates PR after core checks pass
# - alpha → beta: auto-creates PR after alpha tests pass (+ secrets scan)
# - beta → main: auto-creates PR after full tests pass (+ Windows)
#
# Merging to main triggers a release (handled separately by release workflow)
# NOTE: push triggers disabled until staging pipeline is activated.
# To enable: uncomment the push block below.
# push:
# branches:
# - develop
# - alpha
# - beta
# paths-ignore:
# - "docs/**"
# - "*.md"
on:
workflow_dispatch:
inputs:
source_branch:
description: "Source branch to promote from"
required: true
type: choice
options:
- develop
- alpha
- beta
skip_tests:
description: "Skip tests (use with caution)"
required: false
type: boolean
default: false
concurrency:
group: promote-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
jobs:
# Determine promotion target
determine-target:
name: Determine Target Branch
runs-on: ubuntu-latest
outputs:
source: ${{ steps.determine.outputs.source }}
target: ${{ steps.determine.outputs.target }}
test_stage: ${{ steps.determine.outputs.test_stage }}
should_promote: ${{ steps.determine.outputs.should_promote }}
steps:
- name: Determine promotion target
id: determine
run: |
# Get source branch
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
SOURCE="${{ inputs.source_branch }}"
else
SOURCE="${{ github.ref_name }}"
fi
echo "source=$SOURCE" >> $GITHUB_OUTPUT
case "$SOURCE" in
develop)
echo "target=alpha" >> $GITHUB_OUTPUT
echo "test_stage=develop" >> $GITHUB_OUTPUT
echo "should_promote=true" >> $GITHUB_OUTPUT
;;
alpha)
echo "target=beta" >> $GITHUB_OUTPUT
echo "test_stage=alpha" >> $GITHUB_OUTPUT
echo "should_promote=true" >> $GITHUB_OUTPUT
;;
beta)
echo "target=main" >> $GITHUB_OUTPUT
echo "test_stage=beta" >> $GITHUB_OUTPUT
echo "should_promote=true" >> $GITHUB_OUTPUT
;;
*)
echo "target=" >> $GITHUB_OUTPUT
echo "test_stage=" >> $GITHUB_OUTPUT
echo "should_promote=false" >> $GITHUB_OUTPUT
;;
esac
# Ensure target branch exists (create from main if not)
ensure-target-branch:
name: Ensure Target Branch
needs: determine-target
if: needs.determine-target.outputs.should_promote == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create target branch if missing
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TARGET="${{ needs.determine-target.outputs.target }}"
if git ls-remote --exit-code origin "refs/heads/$TARGET" >/dev/null 2>&1; then
echo "Branch '$TARGET' already exists"
else
echo "Branch '$TARGET' does not exist — creating from main"
git push origin "origin/main:refs/heads/$TARGET"
fi
# Run stage-appropriate tests
run-tests:
name: Run Tests
needs: [determine-target, ensure-target-branch]
if: ${{ needs.determine-target.outputs.should_promote == 'true' && (github.event_name != 'workflow_dispatch' || !inputs.skip_tests) }}
uses: ./.github/workflows/testing-strategy.yml
with:
test_stage: ${{ needs.determine-target.outputs.test_stage }}
app_version: ${{ github.sha }}
secrets: inherit
# Create promotion PR
create-promotion-pr:
name: Create Promotion PR
needs: [determine-target, ensure-target-branch, run-tests]
if: |
!cancelled() &&
needs.determine-target.outputs.should_promote == 'true' &&
(needs.run-tests.outputs.test_status == 'passed' || (github.event_name == 'workflow_dispatch' && inputs.skip_tests))
runs-on: ubuntu-latest
outputs:
pr_number: ${{ steps.output-pr.outputs.pull-request-number }}
pr_url: ${{ steps.output-pr.outputs.pull-request-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.determine-target.outputs.source }}
fetch-depth: 0
- name: Get commit info
id: commits
run: |
TARGET="${{ needs.determine-target.outputs.target }}"
# Fetch target branch
git fetch origin $TARGET 2>/dev/null || true
# Get commits not in target
if git rev-parse origin/$TARGET >/dev/null 2>&1; then
COMMIT_COUNT=$(git rev-list --count origin/$TARGET..HEAD 2>/dev/null || echo "0")
COMMIT_SUMMARY=$(git log origin/$TARGET..HEAD --oneline --format="- %s (%h)" 2>/dev/null | head -20 || echo "Initial promotion")
else
COMMIT_COUNT=$(git rev-list --count HEAD 2>/dev/null || echo "0")
COMMIT_SUMMARY=$(git log --oneline --format="- %s (%h)" 2>/dev/null | head -20 || echo "Initial promotion")
fi
echo "count=$COMMIT_COUNT" >> $GITHUB_OUTPUT
DELIM="COMMITS_$(openssl rand -hex 16)"
echo "summary<<${DELIM}" >> $GITHUB_OUTPUT
echo "$COMMIT_SUMMARY" >> $GITHUB_OUTPUT
echo "${DELIM}" >> $GITHUB_OUTPUT
- name: Check for existing PR
id: check-pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SOURCE="${{ needs.determine-target.outputs.source }}"
TARGET="${{ needs.determine-target.outputs.target }}"
EXISTING=$(gh pr list --head "$SOURCE" --base "$TARGET" --json number --jq '.[0].number // empty')
if [ -n "$EXISTING" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "pr_number=$EXISTING" >> $GITHUB_OUTPUT
echo "pr_url=https://github.com/${{ github.repository }}/pull/$EXISTING" >> $GITHUB_OUTPUT
echo "Promotion PR #$EXISTING already exists for $SOURCE → $TARGET"
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Create Pull Request
id: create-pr
if: steps.check-pr.outputs.exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SOURCE="${{ needs.determine-target.outputs.source }}"
TARGET="${{ needs.determine-target.outputs.target }}"
TEST_STAGE="${{ needs.determine-target.outputs.test_stage }}"
COMMIT_COUNT="${{ steps.commits.outputs.count }}"
# Write PR body to a temp file to avoid shell quoting issues
BODY_FILE=$(mktemp)
cat > "$BODY_FILE" <<__PRBODY__
## Staged Promotion
| Property | Value |
|----------|-------|
| Source | \`${SOURCE}\` |
| Target | \`${TARGET}\` |
| Test Stage | \`${TEST_STAGE}\` |
### Changes (${COMMIT_COUNT} commits)
${{ steps.commits.outputs.summary }}
### Checklist
- [ ] Changes reviewed
- [ ] CI passing
- [ ] Ready to promote
---
*Auto-generated by the branch promotion workflow.*
__PRBODY__
PR_URL=$(gh pr create \
--base "$TARGET" \
--head "$SOURCE" \
--title "🚀 Promote: $SOURCE → $TARGET" \
--body-file "$BODY_FILE" \
--label "promotion")
rm -f "$BODY_FILE"
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
echo "Created promotion PR: $SOURCE → $TARGET"
- name: Output existing PR
id: output-pr
run: |
if [ "${{ steps.check-pr.outputs.exists }}" = "true" ]; then
echo "pull-request-number=${{ steps.check-pr.outputs.pr_number }}" >> $GITHUB_OUTPUT
echo "pull-request-url=${{ steps.check-pr.outputs.pr_url }}" >> $GITHUB_OUTPUT
else
echo "pull-request-number=${{ steps.create-pr.outputs.pr_number }}" >> $GITHUB_OUTPUT
echo "pull-request-url=${{ steps.create-pr.outputs.pr_url }}" >> $GITHUB_OUTPUT
fi
# Auto-merge for develop → alpha (fast-track, new PRs only)
auto-merge:
name: Auto-merge (develop → alpha)
needs: [determine-target, create-promotion-pr]
if: |
needs.determine-target.outputs.source == 'develop' &&
needs.create-promotion-pr.outputs.pr_number != '' &&
needs.create-promotion-pr.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Enable auto-merge
run: |
gh pr merge ${{ needs.create-promotion-pr.outputs.pr_number }} \
--auto \
--squash \
--repo ${{ github.repository }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Notify about promotion
notify-promotion:
name: Notify Promotion
needs: [determine-target, create-promotion-pr]
if: "!cancelled() && needs.create-promotion-pr.outputs.pr_url != ''"
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
if: env.DISCORD_WEBHOOK_URL != ''
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "🔄 Promotion PR: ${{ needs.determine-target.outputs.source }} → ${{ needs.determine-target.outputs.target }}"
description: |
**PR**: ${{ needs.create-promotion-pr.outputs.pr_url }}
**Stage**: ${{ needs.determine-target.outputs.test_stage }}
color: "3447003"
# Handle failed tests
notify-failure:
name: Notify Test Failure
needs: [determine-target, run-tests]
if: |
!cancelled() &&
needs.run-tests.outputs.test_status != 'passed' &&
!inputs.skip_tests
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
if: env.DISCORD_WEBHOOK_URL != ''
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "❌ Promotion Blocked: ${{ needs.determine-target.outputs.source }}"
description: |
**Target**: ${{ needs.determine-target.outputs.target }}
**Reason**: Tests failed
[View Logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
color: "15158332"

View File

@@ -0,0 +1,265 @@
name: Release Orchestrator
# Orchestrates staged releases for openclaw
#
# This workflow is called when code is promoted to main (stable release)
# or can be triggered manually for alpha/beta releases from their branches.
#
# Flow: version → changelog → test → deploy → release
# NOTE: push-to-main trigger disabled until staging pipeline is activated.
# To enable: uncomment the push block below.
# push:
# branches:
# - main
# paths-ignore:
# - "docs/**"
# - "*.md"
# - ".github/workflows/docs-*.yml"
on:
workflow_call:
inputs:
release_type:
description: "Release type: alpha, beta, or stable"
required: true
type: string
source_branch:
description: "Source branch for the release"
required: true
type: string
dry_run:
description: "Perform a dry run without publishing"
required: false
type: boolean
default: false
outputs:
version:
description: "The released version"
value: ${{ jobs.version.outputs.new_version }}
release_url:
description: "URL to the GitHub release"
value: ${{ jobs.release.outputs.release_url }}
status:
description: "Release status"
value: ${{ jobs.release.outputs.status }}
secrets:
NPM_TOKEN:
required: false
DISCORD_WEBHOOK_URL:
required: false
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: write
packages: write
jobs:
# Determine release parameters (push vs workflow_call)
determine-params:
name: Determine Parameters
runs-on: ubuntu-latest
outputs:
release_type: ${{ steps.params.outputs.release_type }}
source_branch: ${{ steps.params.outputs.source_branch }}
dry_run: ${{ steps.params.outputs.dry_run }}
steps:
- name: Set parameters
id: params
run: |
# When triggered by push to main, use stable defaults
if [ "${{ github.event_name }}" = "push" ]; then
echo "release_type=stable" >> $GITHUB_OUTPUT
echo "source_branch=main" >> $GITHUB_OUTPUT
echo "dry_run=false" >> $GITHUB_OUTPUT
else
# workflow_call - use provided inputs
echo "release_type=${{ inputs.release_type }}" >> $GITHUB_OUTPUT
echo "source_branch=${{ inputs.source_branch }}" >> $GITHUB_OUTPUT
echo "dry_run=${{ inputs.dry_run }}" >> $GITHUB_OUTPUT
fi
# Get commits since last release
get-commits:
name: Get Commits
needs: determine-params
runs-on: ubuntu-latest
outputs:
commits: ${{ steps.commits.outputs.commits }}
has_changes: ${{ steps.commits.outputs.has_changes }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ needs.determine-params.outputs.source_branch }}
- name: Get commits since last tag
id: commits
run: |
# Get latest tag for this release type
case "${{ needs.determine-params.outputs.release_type }}" in
alpha)
PATTERN="v*-alpha.*"
;;
beta)
PATTERN="v*-beta.*"
;;
stable)
PATTERN="v[0-9]*.[0-9]*.[0-9]*"
;;
esac
# Filter out prerelease tags for stable (glob * matches -alpha/-beta suffixes)
if [ "${{ needs.determine-params.outputs.release_type }}" = "stable" ]; then
LATEST_TAG=$(git tag -l "$PATTERN" --sort=-v:refname | grep -v -E '-(alpha|beta)\.' | head -1)
else
LATEST_TAG=$(git tag -l "$PATTERN" --sort=-v:refname | head -1)
fi
if [ -z "$LATEST_TAG" ]; then
# No previous tag, use all commits
LATEST_TAG=$(git rev-list --max-parents=0 HEAD)
echo "No previous ${{ needs.determine-params.outputs.release_type }} tag found, using initial commit"
else
echo "Latest ${{ needs.determine-params.outputs.release_type }} tag: $LATEST_TAG"
fi
COMMITS=$(git log ${LATEST_TAG}..HEAD --oneline --format="- %s (%h)")
if [ -z "$COMMITS" ]; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "commits=" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
DELIM="COMMITS_$(openssl rand -hex 16)"
echo "commits<<${DELIM}" >> $GITHUB_OUTPUT
echo "$COMMITS" >> $GITHUB_OUTPUT
echo "${DELIM}" >> $GITHUB_OUTPUT
fi
# Version operations
version:
name: Version
needs: [determine-params, get-commits]
if: needs.get-commits.outputs.has_changes == 'true'
uses: ./.github/workflows/version-operations.yml
with:
release_type: ${{ needs.determine-params.outputs.release_type }}
source_branch: ${{ needs.determine-params.outputs.source_branch }}
should_bump: true
dry_run: ${{ needs.determine-params.outputs.dry_run }}
# Generate changelog
changelog:
name: Changelog
needs: [determine-params, get-commits, version]
if: needs.get-commits.outputs.has_changes == 'true'
uses: ./.github/workflows/generate-changelog.yml
with:
version: ${{ needs.version.outputs.new_version }}
release_type: ${{ needs.determine-params.outputs.release_type }}
# Run full test suite for the release type
test:
name: Test
needs: [determine-params, get-commits, version]
if: needs.get-commits.outputs.has_changes == 'true'
uses: ./.github/workflows/testing-strategy.yml
with:
test_stage: ${{ needs.determine-params.outputs.release_type }}
app_version: ${{ needs.version.outputs.new_version }}
secrets: inherit
# Deploy (npm + Docker)
deploy:
name: Deploy
needs: [determine-params, version, test]
if: ${{ needs.determine-params.outputs.dry_run != 'true' && needs.test.outputs.test_status == 'passed' }}
uses: ./.github/workflows/deployment-strategy.yml
with:
deployment_stage: ${{ needs.determine-params.outputs.release_type }}
app_version: ${{ needs.version.outputs.new_version }}
source_branch: ${{ needs.determine-params.outputs.source_branch }}
secrets: inherit
# Create GitHub release
release:
name: GitHub Release
needs: [determine-params, version, changelog, deploy]
if: ${{ needs.determine-params.outputs.dry_run != 'true' }}
runs-on: ubuntu-latest
outputs:
release_url: ${{ steps.create-release.outputs.html_url }}
status: ${{ steps.status.outputs.status }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ needs.determine-params.outputs.source_branch }}
- name: Create GitHub Release
id: create-release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.version.outputs.new_version }}
name: openclaw ${{ needs.version.outputs.new_version }}
body: ${{ needs.changelog.outputs.changelog }}
prerelease: ${{ needs.determine-params.outputs.release_type != 'stable' }}
draft: false
- name: Set status
id: status
run: echo "status=success" >> $GITHUB_OUTPUT
# Notify on success
notify-success:
name: Notify Success
needs: [determine-params, version, release]
if: ${{ !cancelled() && needs.release.result == 'success' }}
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
if: env.DISCORD_WEBHOOK_URL != ''
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "🎉 Released: openclaw v${{ needs.version.outputs.new_version }}"
description: |
**Type**: ${{ needs.determine-params.outputs.release_type }}
**Release**: ${{ needs.release.outputs.release_url }}
color: "3066993"
# Notify on failure
notify-failure:
name: Notify Failure
needs: [determine-params, version, test, deploy, release]
if: |
!cancelled() &&
needs.version.result != 'skipped' &&
(needs.test.result == 'failure' || needs.deploy.result == 'failure' || needs.release.result == 'failure')
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
if: env.DISCORD_WEBHOOK_URL != ''
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "❌ Release Failed: ${{ needs.determine-params.outputs.release_type }}"
description: |
**Branch**: ${{ needs.determine-params.outputs.source_branch }}
**Tests**: ${{ needs.test.outputs.test_status }}
[View Logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
color: "15158332"

51
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Release
# Manual release workflow - triggers the release orchestrator
#
# Branch → Release Type mapping:
# alpha → releases from 'alpha' branch with -alpha.N suffix
# beta → releases from 'beta' branch with -beta.N suffix
# stable → releases from 'main' branch with YYYY.M.D version
on:
workflow_dispatch:
inputs:
release_type:
description: "Release type"
required: true
type: choice
options:
- alpha
- beta
- stable
default: "alpha"
dry_run:
description: "Dry run (no publish)"
required: false
type: boolean
default: false
jobs:
determine-branch:
runs-on: ubuntu-latest
outputs:
branch: ${{ steps.branch.outputs.name }}
steps:
- name: Determine source branch
id: branch
run: |
case "${{ inputs.release_type }}" in
alpha) echo "name=alpha" >> $GITHUB_OUTPUT ;;
beta) echo "name=beta" >> $GITHUB_OUTPUT ;;
stable) echo "name=main" >> $GITHUB_OUTPUT ;;
esac
release:
name: Release
needs: determine-branch
uses: ./.github/workflows/release-orchestrator.yml
with:
release_type: ${{ inputs.release_type }}
source_branch: ${{ needs.determine-branch.outputs.branch }}
dry_run: ${{ inputs.dry_run }}
secrets: inherit

256
.github/workflows/rollback.yml vendored Normal file
View File

@@ -0,0 +1,256 @@
name: Rollback
# Emergency rollback workflow
#
# Reverts npm + Docker to a previous known-good version.
# Does NOT revert git — the bad commits stay in history.
# Create a hotfix branch to fix forward after rolling back.
#
# What it does:
# 1. Re-tags the previous version as @latest / :latest on npm + Docker
# 2. Creates a GitHub release noting the rollback
# 3. Notifies Discord
#
# What it does NOT do:
# - Revert git commits (fix forward instead)
# - Remove the bad version from npm (use `npm unpublish` manually if needed)
on:
workflow_dispatch:
inputs:
rollback_to:
description: "Version to roll back to (e.g. 2026.2.5)"
required: true
type: string
reason:
description: "Reason for rollback"
required: true
type: string
rollback_npm:
description: "Roll back npm dist-tag"
required: false
type: boolean
default: true
rollback_docker:
description: "Roll back Docker :latest tag"
required: false
type: boolean
default: true
concurrency:
group: rollback
cancel-in-progress: false
permissions:
contents: write
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Validate the target version exists
validate:
name: Validate Target Version
runs-on: ubuntu-latest
outputs:
tag_exists: ${{ steps.check.outputs.tag_exists }}
npm_exists: ${{ steps.check.outputs.npm_exists }}
docker_exists: ${{ steps.check.outputs.docker_exists }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate version
id: check
run: |
VERSION="${{ inputs.rollback_to }}"
# Check git tag
if git tag -l "v${VERSION}" | grep -q .; then
echo "tag_exists=true" >> $GITHUB_OUTPUT
echo "✅ Git tag v${VERSION} exists"
else
echo "tag_exists=false" >> $GITHUB_OUTPUT
echo "❌ Git tag v${VERSION} not found"
fi
# Check npm
if npm view "openclaw@${VERSION}" version 2>/dev/null | grep -q "${VERSION}"; then
echo "npm_exists=true" >> $GITHUB_OUTPUT
echo "✅ npm version ${VERSION} exists"
else
echo "npm_exists=false" >> $GITHUB_OUTPUT
echo "⚠️ npm version ${VERSION} not found (npm rollback will be skipped)"
fi
# Check Docker
if docker manifest inspect "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}" >/dev/null 2>&1; then
echo "docker_exists=true" >> $GITHUB_OUTPUT
echo "✅ Docker image ${VERSION} exists"
else
echo "docker_exists=false" >> $GITHUB_OUTPUT
echo "⚠️ Docker image ${VERSION} not found (Docker rollback will be skipped)"
fi
- name: Fail if tag doesn't exist
if: steps.check.outputs.tag_exists != 'true'
run: |
echo "::error::Version v${{ inputs.rollback_to }} does not exist as a git tag"
exit 1
# Roll back npm dist-tag
rollback-npm:
name: Rollback npm
needs: validate
if: ${{ inputs.rollback_npm && needs.validate.outputs.npm_exists == 'true' }}
runs-on: ubuntu-latest
outputs:
status: ${{ steps.rollback.outputs.status }}
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22.x
registry-url: "https://registry.npmjs.org"
- name: Roll back npm @latest tag
id: rollback
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
VERSION="${{ inputs.rollback_to }}"
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "::warning::NPM_TOKEN not set, skipping npm rollback"
echo "status=skipped" >> $GITHUB_OUTPUT
exit 0
fi
# Move the @latest dist-tag to the rollback version
if npm dist-tag add "openclaw@${VERSION}" latest; then
echo "status=success" >> $GITHUB_OUTPUT
echo "✅ npm @latest now points to ${VERSION}"
# Show current dist-tags for verification
npm dist-tag ls openclaw
else
echo "status=failed" >> $GITHUB_OUTPUT
echo "::error::Failed to update npm dist-tag"
exit 1
fi
# Roll back Docker :latest tag
rollback-docker:
name: Rollback Docker
needs: validate
if: ${{ inputs.rollback_docker && needs.validate.outputs.docker_exists == 'true' }}
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
outputs:
status: ${{ steps.rollback.outputs.status }}
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Roll back Docker :latest tag
id: rollback
run: |
VERSION="${{ inputs.rollback_to }}"
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
# Re-tag the rollback version as :latest
if docker buildx imagetools create -t "${IMAGE}:latest" "${IMAGE}:${VERSION}"; then
echo "status=success" >> $GITHUB_OUTPUT
echo "✅ Docker :latest now points to ${VERSION}"
else
echo "status=failed" >> $GITHUB_OUTPUT
echo "::error::Failed to retag Docker image"
exit 1
fi
# Create rollback release note
create-rollback-release:
name: Create Rollback Release
needs: [validate, rollback-npm, rollback-docker]
if: "!cancelled() && needs.validate.result == 'success'"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get current version
id: current
run: |
CURRENT=$(node -p "require('./package.json').version")
echo "version=$CURRENT" >> $GITHUB_OUTPUT
- name: Create rollback release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ inputs.rollback_to }}
name: "⚠️ Rollback to openclaw ${{ inputs.rollback_to }}"
body: |
## ⚠️ Rollback
| Property | Value |
|----------|-------|
| Rolled back from | `${{ steps.current.outputs.version }}` |
| Rolled back to | `${{ inputs.rollback_to }}` |
| Initiated by | @${{ github.actor }} |
### Reason
${{ inputs.reason }}
### Rollback Status
| Target | Status |
|--------|--------|
| npm @latest | ${{ needs.rollback-npm.outputs.status || 'skipped' }} |
| Docker :latest | ${{ needs.rollback-docker.outputs.status || 'skipped' }} |
### Next Steps
1. Investigate the issue in the rolled-back version
2. Create a `hotfix/*` branch with the fix
3. Merge via the hotfix workflow to restore forward progress
---
*This release was created by the rollback workflow.*
make_latest: false
prerelease: false
# Notify
notify:
name: Discord Notification
needs: [validate, rollback-npm, rollback-docker, create-rollback-release]
if: "!cancelled() && needs.validate.result == 'success'"
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord notification
if: env.DISCORD_WEBHOOK_URL != ''
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "⚠️ ROLLBACK: openclaw → v${{ inputs.rollback_to }}"
description: |
**Reason**: ${{ inputs.reason }}
**Initiated by**: @${{ github.actor }}
**npm**: ${{ needs.rollback-npm.outputs.status || 'skipped' }}
**Docker**: ${{ needs.rollback-docker.outputs.status || 'skipped' }}
color: "15105570"

153
.github/workflows/testing-strategy.yml vendored Normal file
View File

@@ -0,0 +1,153 @@
name: Testing Strategy
# Reusable testing workflow for staged releases
# Passes test_stage to ci.yml to control which platform tests run
#
# Progressive test coverage by stage:
# - develop/alpha: core checks + secrets + android
# - beta: + Windows tests
# - stable: + macOS tests, macOS app, install smoke
on:
workflow_call:
inputs:
test_stage:
description: "Testing stage: develop, alpha, beta, or stable"
required: true
type: string
app_version:
description: "Version of the application being tested"
required: false
type: string
default: "dev"
outputs:
test_status:
description: "Overall test status"
value: ${{ jobs.test-summary.outputs.overall_status }}
secrets:
DISCORD_WEBHOOK_URL:
required: false
jobs:
# Run CI with stage-appropriate platform gates
ci:
name: Core CI
uses: ./.github/workflows/ci.yml
with:
test_stage: ${{ inputs.test_stage }}
secrets: inherit
# Install smoke test (stable only)
install-smoke:
name: Install Smoke Test
if: inputs.test_stage == 'stable'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm (corepack retry)
run: |
set -euo pipefail
corepack enable
for attempt in 1 2 3; do
if corepack prepare pnpm@10.23.0 --activate; then
pnpm -v
exit 0
fi
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
sleep $((attempt * 10))
done
exit 1
- name: Install pnpm deps (minimal)
run: pnpm install --ignore-scripts --frozen-lockfile
- name: Run installer smoke tests
env:
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: "1"
CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
run: pnpm test:install:smoke
# Test summary
test-summary:
name: Test Summary (${{ inputs.test_stage }})
runs-on: ubuntu-latest
needs: [ci, install-smoke]
if: "!cancelled()"
outputs:
overall_status: ${{ steps.summary.outputs.overall_status }}
steps:
- name: Generate summary
id: summary
run: |
echo "## 🧪 Test Results - ${{ inputs.test_stage }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Test Suite | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| CI (checks + secrets) | ${{ needs.ci.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Install Smoke | ${{ needs.install-smoke.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
# CI must pass (includes platform-specific jobs based on test_stage)
if [ "${{ needs.ci.result }}" != "success" ]; then
echo "overall_status=failed" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ❌ CI failed" >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Stage-specific checks
STAGE="${{ inputs.test_stage }}"
FAILED=false
if [ "$STAGE" = "stable" ]; then
if [ "${{ needs.install-smoke.result }}" = "failure" ]; then
FAILED=true
fi
fi
if [ "$FAILED" = "true" ]; then
echo "overall_status=failed" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ❌ Some stage-specific tests failed" >> $GITHUB_STEP_SUMMARY
else
echo "overall_status=passed" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ✅ All required tests passed!" >> $GITHUB_STEP_SUMMARY
fi
# Discord notifications
notify:
name: Discord Notification
needs: test-summary
if: "!cancelled()"
runs-on: ubuntu-latest
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Discord success notification
if: ${{ env.DISCORD_WEBHOOK_URL != '' && needs.test-summary.outputs.overall_status == 'passed' }}
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "✅ Tests Passed: ${{ inputs.test_stage }} v${{ inputs.app_version }}"
description: "All tests passed for ${{ inputs.test_stage }} stage!"
color: "3066993"
- name: Discord failure notification
if: ${{ env.DISCORD_WEBHOOK_URL != '' && needs.test-summary.outputs.overall_status != 'passed' }}
uses: ./.github/actions/discord-notify
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
title: "❌ Tests Failed: ${{ inputs.test_stage }} v${{ inputs.app_version }}"
description: |
Some tests failed for ${{ inputs.test_stage }} stage.
[View Logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
color: "15158332"

188
.github/workflows/version-operations.yml vendored Normal file
View File

@@ -0,0 +1,188 @@
name: Version Operations
# Version bump workflow for openclaw
#
# Version format: YYYY.M.D (stable) or YYYY.M.D-{alpha,beta}.N (prerelease)
# Examples: 2026.2.6, 2026.2.6-alpha.1, 2026.2.6-beta.3
on:
workflow_call:
inputs:
release_type:
description: "Release type: alpha, beta, or stable"
required: true
type: string
source_branch:
description: "Source branch"
required: true
type: string
should_bump:
description: "Whether to bump the version"
required: false
type: boolean
default: true
dry_run:
description: "Perform a dry run without committing"
required: false
type: boolean
default: false
outputs:
current_version:
description: "Current version before bump"
value: ${{ jobs.version.outputs.current_version }}
new_version:
description: "New version after bump"
value: ${{ jobs.version.outputs.new_version }}
version_tag:
description: "Version tag (with v prefix)"
value: ${{ jobs.version.outputs.version_tag }}
permissions:
contents: write
jobs:
version:
name: Version Operations
runs-on: ubuntu-latest
outputs:
current_version: ${{ steps.get-version.outputs.current }}
new_version: ${{ steps.bump-version.outputs.new }}
version_tag: ${{ steps.bump-version.outputs.tag }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.source_branch }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Get current version
id: get-version
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "Current version: $CURRENT_VERSION"
- name: Calculate new version
id: bump-version
run: |
CURRENT="${{ steps.get-version.outputs.current }}"
RELEASE_TYPE="${{ inputs.release_type }}"
# Get current date components
YEAR=$(date +%Y)
MONTH=$(date +%-m)
DAY=$(date +%-d)
TODAY="${YEAR}.${MONTH}.${DAY}"
# Parse current version to check if it's today + same type
# Patterns: YYYY.M.D or YYYY.M.D-type.N
if [[ "$CURRENT" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-([a-z]+)\.([0-9]+))?$ ]]; then
CURR_DATE="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}"
CURR_TYPE="${BASH_REMATCH[5]}"
CURR_NUM="${BASH_REMATCH[6]:-0}"
else
CURR_DATE=""
CURR_TYPE=""
CURR_NUM=0
fi
case "$RELEASE_TYPE" in
alpha)
if [ "$CURR_DATE" = "$TODAY" ] && [ "$CURR_TYPE" = "alpha" ]; then
# Same day, same type - increment prerelease number
NEW_NUM=$((CURR_NUM + 1))
else
# New day or different type - start at 1
NEW_NUM=1
fi
NEW_VERSION="${TODAY}-alpha.${NEW_NUM}"
;;
beta)
if [ "$CURR_DATE" = "$TODAY" ] && [ "$CURR_TYPE" = "beta" ]; then
NEW_NUM=$((CURR_NUM + 1))
else
NEW_NUM=1
fi
NEW_VERSION="${TODAY}-beta.${NEW_NUM}"
;;
stable)
# Stable releases use date; append counter if tag already exists
if git tag -l "v${TODAY}" | grep -q .; then
# Tag exists, find next available counter
COUNTER=1
while git tag -l "v${TODAY}.${COUNTER}" | grep -q .; do
COUNTER=$((COUNTER + 1))
done
NEW_VERSION="${TODAY}.${COUNTER}"
else
NEW_VERSION="${TODAY}"
fi
;;
*)
echo "Unknown release type: $RELEASE_TYPE"
exit 1
;;
esac
echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
echo "New version: $NEW_VERSION"
- name: Update package.json
if: ${{ inputs.should_bump && !inputs.dry_run }}
run: |
NEW_VERSION="${{ steps.bump-version.outputs.new }}"
# Update package.json version
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
pkg.version = '$NEW_VERSION';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
"
echo "Updated package.json to version $NEW_VERSION"
- name: Sync extension versions
if: ${{ inputs.should_bump && !inputs.dry_run }}
run: |
# Run plugins:sync if available (aligns extension package versions)
if npm run --silent plugins:sync 2>/dev/null; then
echo "Extension versions synced"
else
echo "plugins:sync not available, skipping"
fi
- name: Commit version bump
if: ${{ inputs.should_bump && !inputs.dry_run }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
NEW_VERSION="${{ steps.bump-version.outputs.new }}"
# Stage all version-related changes
git add package.json
git add extensions/*/package.json 2>/dev/null || true
# Check if there are changes to commit
if git diff --cached --quiet; then
echo "No version changes to commit"
else
git commit -m "chore: bump version to $NEW_VERSION"
git push origin ${{ inputs.source_branch }}
fi
- name: Create tag
if: ${{ inputs.should_bump && !inputs.dry_run }}
run: |
TAG="${{ steps.bump-version.outputs.tag }}"
git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"

View File

@@ -3,6 +3,11 @@ name: Workflow Sanity
on:
pull_request:
push:
branches: [main]
concurrency:
group: workflow-sanity-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
no-tabs:

View File

@@ -24,6 +24,7 @@
"assets/",
"dist/",
"docs/_layouts/",
"extensions/",
"node_modules/",
"patches/",
"pnpm-lock.yaml/",

View File

@@ -2,29 +2,54 @@
Docs: https://docs.openclaw.ai
## 2026.2.4
## 2026.2.6
### Changes
- Cron: default `wakeMode` is now `"now"` for new jobs (was `"next-heartbeat"`). (#10776) Thanks @tyler6204.
- Cron: `cron run` defaults to force execution; use `--due` to restrict to due-only. (#10776) Thanks @tyler6204.
- Models: support Anthropic Opus 4.6 and OpenAI Codex gpt-5.3-codex (forward-compat fallbacks). (#9853, #10720, #9995) Thanks @TinyTb, @calvin-hpnet, @tyler6204.
- Providers: add xAI (Grok) support. (#9885) Thanks @grp06.
- Providers: add Baidu Qianfan support. (#8868) Thanks @ide-rea.
- Web UI: add token usage dashboard. (#10072) Thanks @Takhoffman.
- Memory: native Voyage AI support. (#7078) Thanks @mcinteerj.
- Sessions: cap sessions_history payloads to reduce context overflow. (#10000) Thanks @gut-puncture.
- CLI: sort commands alphabetically in help output. (#8068) Thanks @deepsoumya617.
- CI: optimize pipeline throughput (macOS consolidation, Windows perf, workflow concurrency). (#10784) Thanks @mcaxtr.
- Agents: bump pi-mono to 0.52.7; add embedded forward-compat fallback for Opus 4.6 model ids.
### Added
- Cron: run history deep-links to session chat from the dashboard. (#10776) Thanks @tyler6204.
- Cron: per-run session keys in run log entries and default labels for cron sessions. (#10776) Thanks @tyler6204.
- Cron: legacy payload field compatibility (`deliver`, `channel`, `to`, `bestEffortDeliver`) in schema. (#10776) Thanks @tyler6204.
### Fixes
- Cron: scheduler reliability (timer drift, restart catch-up, lock contention, stale running markers). (#10776) Thanks @tyler6204.
- Cron: store migration hardening (legacy field migration, parse error handling, explicit delivery mode persistence). (#10776) Thanks @tyler6204.
- Memory: set Voyage embeddings `input_type` for improved retrieval. (#10818) Thanks @mcinteerj.
- Telegram: auto-inject DM topic threadId in message tool + subagent announce. (#7235) Thanks @Lukavyi.
- Security: require auth for Gateway canvas host and A2UI assets. (#9518) Thanks @coygeek.
- Cron: fix scheduling and reminder delivery regressions; harden next-run recompute + timer re-arming + legacy schedule fields. (#9733, #9823, #9948, #9932) Thanks @tyler6204, @pycckuu, @j2h4u, @fujiwara-tofu-shop.
- Update: harden Control UI asset handling in update flow. (#10146) Thanks @gumadeiras.
- Security: add skill/plugin code safety scanner; redact credentials from config.get gateway responses. (#9806, #9858) Thanks @abdelsfane.
- Exec approvals: coerce bare string allowlist entries to objects. (#9903) Thanks @mcaxtr.
- Slack: add mention stripPatterns for /new and /reset. (#9971) Thanks @ironbyte-rgb.
- Chrome extension: fix bundled path resolution. (#8914) Thanks @kelvinCB.
- Compaction/errors: allow multiple compaction retries on context overflow; show clear billing errors. (#8928, #8391) Thanks @Glucksberg.
## 2026.2.3
### Changes
- Agents: bump pi-mono packages to 0.52.5. (#9949) Thanks @gumadeiras.
- Models: default Anthropic model to `anthropic/claude-opus-4-6`. (#9853) Thanks @TinyTb.
- Models/Onboarding: refresh provider defaults, update OpenAI/OpenAI Codex wizard defaults, and harden model allowlist initialization for first-time configs with matching docs/tests. (#9911) Thanks @gumadeiras.
- Telegram: auto-inject forum topic `threadId` in message tool and subagent announce so media, buttons, and subagent results land in the correct topic instead of General. (#7235) Thanks @Lukavyi.
- Security: add skill/plugin code safety scanner that detects dangerous patterns (command injection, eval, data exfiltration, obfuscated code, crypto mining, env harvesting) in installed extensions. Integrated into `openclaw security audit --deep` and plugin install flow; scan failures surface as warnings. (#9806) Thanks @abdelsfane.
- CLI: sort `openclaw --help` commands (and options) alphabetically. (#8068) Thanks @deepsoumya617.
- Telegram: remove last `@ts-nocheck` from `bot-handlers.ts`, use Grammy types directly, deduplicate `StickerMetadata`. Zero `@ts-nocheck` remaining in `src/telegram/`. (#9206)
- Telegram: remove `@ts-nocheck` from `bot-message.ts`, type deps via `Omit<BuildTelegramMessageContextParams>`, widen `allMedia` to `TelegramMediaRef[]`. (#9180)
- Telegram: remove `@ts-nocheck` from `bot.ts`, fix duplicate `bot.catch` error handler (Grammy overrides), remove dead reaction `message_thread_id` routing, harden sticker cache guard. (#9077)
- Telegram: allow per-group and per-topic `groupPolicy` overrides under `channels.telegram.groups`. (#9775) Thanks @nicolasstanley.
- Feishu: expand channel handling (posts with images, doc links, routing, reactions/typing, replies, native commands). (#8975) Thanks @jiulingyun.
- Onboarding: add Cloudflare AI Gateway provider setup and docs. (#7914) Thanks @roerohan.
- Onboarding: add Moonshot (.cn) auth choice and keep the China base URL when preserving defaults. (#7180) Thanks @waynelwz.
- Onboarding: add xAI (Grok) auth choice and provider defaults. (#9885) Thanks @grp06.
- Docs: clarify tmux send-keys for TUI by splitting text and Enter. (#7737) Thanks @Wangnov.
- Web UI: add Token Usage dashboard with session analytics. (#8462) Thanks @mcinteerj.
- Docs: mirror the landing page revamp for zh-CN (features, quickstart, docs directory, network model, credits). (#8994) Thanks @joshp123.
- Docs: strengthen secure DM mode guidance for multi-user inboxes with an explicit warning and example. (#9377) Thanks @Shrinija17.
- Docs: document `activeHours` heartbeat field with timezone resolution chain and example. (#9366) Thanks @unisone.
- Messages: add per-channel and per-account responsePrefix overrides across channels. (#9001) Thanks @mudrii.
- Cron: add announce delivery mode for isolated jobs (CLI + Control UI) and delivery mode config.
- Cron: default isolated jobs to announce delivery; accept ISO 8601 `schedule.at` in tool inputs.
@@ -35,34 +60,15 @@ Docs: https://docs.openclaw.ai
### Fixes
- Control UI: add hardened fallback for asset resolution in global npm installs. (#4855) Thanks @anapivirtua.
- Update: remove dead restore control-ui step that failed on gitignored dist/ output.
- Update: avoid wiping prebuilt Control UI assets during dev auto-builds (`tsdown --no-clean`), run update doctor via `openclaw.mjs`, and auto-restore missing UI assets after doctor. (#10146) Thanks @gumadeiras.
- Models: add forward-compat fallback for `openai-codex/gpt-5.3-codex` when model registry hasn't discovered it yet. (#9989) Thanks @w1kke.
- Auto-reply/Docs: normalize `extra-high` (and spaced variants) to `xhigh` for Codex thinking levels, and align Codex 5.3 FAQ examples. (#9976) Thanks @slonce70.
- Compaction: remove orphaned `tool_result` messages during history pruning to prevent session corruption from aborted tool calls. (#9868, fixes #9769, #9724, #9672)
- Telegram: pass `parentPeer` for forum topic binding inheritance so group-level bindings apply to all topics within the group. (#9789, fixes #9545, #9351)
- CLI: pass `--disable-warning=ExperimentalWarning` as a Node CLI option when respawning (avoid disallowed `NODE_OPTIONS` usage; fixes npm pack). (#9691) Thanks @18-RAJAT.
- CLI: resolve bundled Chrome extension assets by walking up to the nearest assets directory; add resolver and clipboard tests. (#8914) Thanks @kelvinCB.
- Tests: stabilize Windows ACL coverage with deterministic os.userInfo mocking. (#9335) Thanks @M00N7682.
- Exec approvals: coerce bare string allowlist entries to objects to prevent allowlist corruption. (#9903, fixes #9790) Thanks @mcaxtr.
- Exec approvals: ensure two-phase approval registration/decision flow works reliably by validating `twoPhase` requests and exposing `waitDecision` as an approvals-scoped gateway method. (#3357, fixes #2402) Thanks @ramin-shirali.
- Heartbeat: allow explicit accountId routing for multi-account channels. (#8702) Thanks @lsh411.
- TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras.
- Security: stop exposing Gateway auth tokens via URL query parameters in Control UI entrypoints, and reject hook tokens in query parameters. (#9436) Thanks @coygeek.
- Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.
- Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.
- Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman.
- Web UI: resolve header logo path when `gateway.controlUi.basePath` is set. (#7178) Thanks @Yeom-JinHo.
- Web UI: apply button styling to the new-messages indicator.
- Onboarding: infer auth choice from non-interactive API key flags. (#8484) Thanks @f-trycua.
- Usage: include estimated cost when breakdown is missing and keep `usage.cost` days support. (#8462) Thanks @mcinteerj.
- Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.
- Security: redact channel credentials (tokens, passwords, API keys, secrets) from gateway config APIs and preserve secrets during Control UI round-trips. (#9858) Thanks @abdelsfane.
- Discord: treat allowlisted senders as owner for system-prompt identity hints while keeping channel topics untrusted.
- Slack: strip `<@...>` mention tokens before command matching so `/new` and `/reset` work when prefixed with a mention. (#9971) Thanks @ironbyte-rgb.
- Agents: cap `sessions_history` tool output and strip oversized fields to prevent context overflow. (#10000) Thanks @gut-puncture.
- Security: normalize code safety finding paths in `openclaw security audit --deep` output for cross-platform consistency. (#10000) Thanks @gut-puncture.
- Security: enforce sandboxed media paths for message tool attachments. (#9182) Thanks @victormier.
- Security: require explicit credentials for gateway URL overrides to prevent credential leakage. (#8113) Thanks @victormier.
- Security: gate `whatsapp_login` tool to owner senders and default-deny non-owner contexts. (#8768) Thanks @victormier.
@@ -70,13 +76,9 @@ Docs: https://docs.openclaw.ai
- Voice call: add regression coverage for anonymous inbound caller IDs with allowlist policy. (#8104) Thanks @victormier.
- Cron: accept epoch timestamps and 0ms durations in CLI `--at` parsing.
- Cron: reload store data when the store file is recreated or mtime changes.
- Cron: prevent `recomputeNextRuns` from skipping due jobs when timer fires late by reordering `onTimer` flow. (#9823, fixes #9788) Thanks @pycckuu.
- Cron: deliver announce runs directly, honor delivery mode, and respect wakeMode for summaries. (#8540) Thanks @tyler6204.
- Cron: correct announce delivery inference for thread session keys and null delivery inputs. (#9733) Thanks @tyler6204.
- Telegram: include forward_from_chat metadata in forwarded messages and harden cron delivery target checks. (#8392) Thanks @Glucksberg.
- Telegram: preserve DM topic threadId in deliveryContext. (#9039) Thanks @lailoo.
- macOS: fix cron payload summary rendering and ISO 8601 formatter concurrency safety.
- Security: require gateway auth for Canvas host and A2UI assets. (#9518) Thanks @coygeek.
## 2026.2.2-3

View File

@@ -25,6 +25,9 @@ Welcome to the lobster tank! 🦞
- **Gustavo Madeira Santana** - Multi-agents, CLI, web UI
- GitHub: [@gumadeiras](https://github.com/gumadeiras) · X: [@gumadeiras](https://x.com/gumadeiras)
- **Maximilian Nussbaumer** - DevOps, CI/CD
- GitHub: [@quotentiroler](https://github.com/quotentiroler)
## How to Contribute
1. **Bugs & small fixes** → Open a PR!
@@ -76,3 +79,46 @@ We are currently prioritizing:
- **Performance**: Optimizing token usage and compaction logic.
Check the [GitHub Issues](https://github.com/openclaw/openclaw/issues) for "good first issue" labels!
## Core vs ClawHub
Not everything belongs in the main repo. Here's how to decide:
| Belongs in **Core** | Belongs on **[ClawHub](https://clawhub.ai)** |
| ---------------------------------------------- | ---------------------------------------------------- |
| Channel integrations (Telegram, Discord, etc.) | Domain-specific skills (QR codes, image tools, etc.) |
| CLI commands and infrastructure | Custom workflows and automations |
| Provider integrations (LLM backends) | Niche or experimental utilities |
| Security, routing, and core plumbing | Third-party service integrations |
**Rule of thumb:** if it adds new dependencies or is useful to some users but not most, it belongs on ClawHub. When in doubt, ask in Discord or open a Discussion before writing code.
Skills submitted as PRs to this repo will be redirected to ClawHub. If the core maintainers later decide certain functionality should be first-party, it will be integrated into core.
## Branch Strategy
> **Note:** The staged promotion pipeline is not yet active. Workflows are in
> place but dormant. For now, open PRs targeting `main` as usual. Once the
> pipeline is activated, the flow below will apply.
We will use staged branch promotion to keep `main` stable:
```
dev/* / feature/* / fix/* → develop → alpha → beta → main
```
### For External Contributors (once pipeline is active)
1. Fork the repo
2. Create your branch (`dev/my-feature`, `fix/some-bug`, etc.)
3. Open a PR targeting `develop` (not `main`)
4. CI runs lightweight checks only — no heavy platform tests on your PR
5. Once merged to `develop`, your changes promote through alpha → beta → main automatically
### For Maintainers (once pipeline is active)
- **Regular changes**: merge to `develop`, let the pipeline promote
- **Hotfixes**: use `hotfix/*` branches for emergency fixes that bypass staging directly to `main`
- **Docs-only changes**: skip the test pipeline automatically (paths-ignore)
See [Pipeline docs](https://docs.openclaw.ai/reference/pipeline) for full details.

View File

@@ -44,5 +44,5 @@ USER node
#
# For container platforms requiring external health checks:
# 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var
# 2. Override CMD: ["node","dist/index.js","gateway","--allow-unconfigured","--bind","lan"]
CMD ["node", "dist/index.js", "gateway", "--allow-unconfigured"]
# 2. Override CMD: ["node","openclaw.mjs","gateway","--allow-unconfigured","--bind","lan"]
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]

View File

@@ -22,7 +22,7 @@ android {
minSdk = 31
targetSdk = 36
versionCode = 202602030
versionName = "2026.2.4"
versionName = "2026.2.6"
}
buildTypes {

View File

@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.4</string>
<string>2026.2.6</string>
<key>CFBundleVersion</key>
<string>20260202</string>
<key>NSAppTransportSecurity</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.4</string>
<string>2026.2.6</string>
<key>CFBundleVersion</key>
<string>20260202</string>
</dict>

View File

@@ -81,7 +81,7 @@ targets:
properties:
CFBundleDisplayName: OpenClaw
CFBundleIconName: AppIcon
CFBundleShortVersionString: "2026.2.4"
CFBundleShortVersionString: "2026.2.6"
CFBundleVersion: "20260202"
UILaunchScreen: {}
UIApplicationSceneManifest:
@@ -130,5 +130,5 @@ targets:
path: Tests/Info.plist
properties:
CFBundleDisplayName: OpenClawTests
CFBundleShortVersionString: "2026.2.4"
CFBundleShortVersionString: "2026.2.6"
CFBundleVersion: "20260202"

View File

@@ -29,7 +29,7 @@ struct CronJobEditor: View {
@State var agentId: String = ""
@State var enabled: Bool = true
@State var sessionTarget: CronSessionTarget = .main
@State var wakeMode: CronWakeMode = .nextHeartbeat
@State var wakeMode: CronWakeMode = .now
@State var deleteAfterRun: Bool = false
enum ScheduleKind: String, CaseIterable, Identifiable { case at, every, cron; var id: String { rawValue } }
@@ -119,8 +119,8 @@ struct CronJobEditor: View {
GridRow {
self.gridLabel("Wake mode")
Picker("", selection: self.$wakeMode) {
Text("next-heartbeat").tag(CronWakeMode.nextHeartbeat)
Text("now").tag(CronWakeMode.now)
Text("next-heartbeat").tag(CronWakeMode.nextHeartbeat)
}
.labelsHidden()
.pickerStyle(.segmented)

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.2.4</string>
<string>2026.2.6</string>
<key>CFBundleVersion</key>
<string>202602020</string>
<key>CFBundleIconFile</key>

View File

@@ -2025,6 +2025,8 @@ public struct CronRunLogEntry: Codable, Sendable {
public let status: AnyCodable?
public let error: String?
public let summary: String?
public let sessionid: String?
public let sessionkey: String?
public let runatms: Int?
public let durationms: Int?
public let nextrunatms: Int?
@@ -2036,6 +2038,8 @@ public struct CronRunLogEntry: Codable, Sendable {
status: AnyCodable?,
error: String?,
summary: String?,
sessionid: String?,
sessionkey: String?,
runatms: Int?,
durationms: Int?,
nextrunatms: Int?
@@ -2046,6 +2050,8 @@ public struct CronRunLogEntry: Codable, Sendable {
self.status = status
self.error = error
self.summary = summary
self.sessionid = sessionid
self.sessionkey = sessionkey
self.runatms = runatms
self.durationms = durationms
self.nextrunatms = nextrunatms
@@ -2057,6 +2063,8 @@ public struct CronRunLogEntry: Codable, Sendable {
case status
case error
case summary
case sessionid = "sessionId"
case sessionkey = "sessionKey"
case runatms = "runAtMs"
case durationms = "durationMs"
case nextrunatms = "nextRunAtMs"

View File

@@ -2025,6 +2025,8 @@ public struct CronRunLogEntry: Codable, Sendable {
public let status: AnyCodable?
public let error: String?
public let summary: String?
public let sessionid: String?
public let sessionkey: String?
public let runatms: Int?
public let durationms: Int?
public let nextrunatms: Int?
@@ -2036,6 +2038,8 @@ public struct CronRunLogEntry: Codable, Sendable {
status: AnyCodable?,
error: String?,
summary: String?,
sessionid: String?,
sessionkey: String?,
runatms: Int?,
durationms: Int?,
nextrunatms: Int?
@@ -2046,6 +2050,8 @@ public struct CronRunLogEntry: Codable, Sendable {
self.status = status
self.error = error
self.summary = summary
self.sessionid = sessionid
self.sessionkey = sessionkey
self.runatms = runatms
self.durationms = durationms
self.nextrunatms = nextrunatms
@@ -2057,6 +2063,8 @@ public struct CronRunLogEntry: Codable, Sendable {
case status
case error
case summary
case sessionid = "sessionId"
case sessionkey = "sessionKey"
case runatms = "runAtMs"
case durationms = "durationMs"
case nextrunatms = "nextRunAtMs"

View File

@@ -17,6 +17,8 @@ the right time, and can optionally deliver output back to a chat.
If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
cron is the mechanism.
Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
## TL;DR
- Cron runs **inside the Gateway** (not inside the model).
@@ -40,7 +42,7 @@ openclaw cron add \
--delete-after-run
openclaw cron list
openclaw cron run <job-id> --force
openclaw cron run <job-id>
openclaw cron runs --id <job-id>
```
@@ -123,8 +125,8 @@ local timezone is used.
Main jobs enqueue a system event and optionally wake the heartbeat runner.
They must use `payload.kind = "systemEvent"`.
- `wakeMode: "next-heartbeat"` (default): event waits for the next scheduled heartbeat.
- `wakeMode: "now"`: event triggers an immediate heartbeat run.
- `wakeMode: "now"` (default): event triggers an immediate heartbeat run.
- `wakeMode: "next-heartbeat"`: event waits for the next scheduled heartbeat.
This is the best fit when you want the normal heartbeat prompt + main-session context.
See [Heartbeat](/gateway/heartbeat).
@@ -288,7 +290,7 @@ Notes:
- `sessionTarget` must be `"main"` or `"isolated"` and must match `payload.kind`.
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun` (defaults to true for `at`),
`delivery`.
- `wakeMode` defaults to `"next-heartbeat"` when omitted.
- `wakeMode` defaults to `"now"` when omitted.
### cron.update params
@@ -420,10 +422,11 @@ openclaw cron edit <jobId> --agent ops
openclaw cron edit <jobId> --clear-agent
```
Manual run (debug):
Manual run (force is the default, use `--due` to only run when due):
```bash
openclaw cron run <jobId> --force
openclaw cron run <jobId>
openclaw cron run <jobId> --due
```
Edit an existing job (patch fields):

View File

@@ -0,0 +1,122 @@
---
summary: "Troubleshoot cron and heartbeat scheduling and delivery"
read_when:
- Cron did not run
- Cron ran but no message was delivered
- Heartbeat seems silent or skipped
title: "Automation Troubleshooting"
---
# Automation troubleshooting
Use this page for scheduler and delivery issues (`cron` + `heartbeat`).
## Command ladder
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Then run automation checks:
```bash
openclaw cron status
openclaw cron list
openclaw system heartbeat last
```
## Cron not firing
```bash
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw logs --follow
```
Good output looks like:
- `cron status` reports enabled and a future `nextWakeAtMs`.
- Job is enabled and has a valid schedule/timezone.
- `cron runs` shows `ok` or explicit skip reason.
Common signatures:
- `cron: scheduler disabled; jobs will not run automatically` → cron disabled in config/env.
- `cron: timer tick failed` → scheduler tick crashed; inspect surrounding stack/log context.
- `reason: not-due` in run output → manual run called without `--force` and job not due yet.
## Cron fired but no delivery
```bash
openclaw cron runs --id <jobId> --limit 20
openclaw cron list
openclaw channels status --probe
openclaw logs --follow
```
Good output looks like:
- Run status is `ok`.
- Delivery mode/target are set for isolated jobs.
- Channel probe reports target channel connected.
Common signatures:
- Run succeeded but delivery mode is `none` → no external message is expected.
- Delivery target missing/invalid (`channel`/`to`) → run may succeed internally but skip outbound.
- Channel auth errors (`unauthorized`, `missing_scope`, `Forbidden`) → delivery blocked by channel credentials/permissions.
## Heartbeat suppressed or skipped
```bash
openclaw system heartbeat last
openclaw logs --follow
openclaw config get agents.defaults.heartbeat
openclaw channels status --probe
```
Good output looks like:
- Heartbeat enabled with non-zero interval.
- Last heartbeat result is `ran` (or skip reason is understood).
Common signatures:
- `heartbeat skipped` with `reason=quiet-hours` → outside `activeHours`.
- `requests-in-flight` → main lane busy; heartbeat deferred.
- `empty-heartbeat-file``HEARTBEAT.md` exists but has no actionable content.
- `alerts-disabled` → visibility settings suppress outbound heartbeat messages.
## Timezone and activeHours gotchas
```bash
openclaw config get agents.defaults.heartbeat.activeHours
openclaw config get agents.defaults.heartbeat.activeHours.timezone
openclaw config get agents.defaults.userTimezone || echo "agents.defaults.userTimezone not set"
openclaw cron list
openclaw logs --follow
```
Quick rules:
- `Config path not found: agents.defaults.userTimezone` means the key is unset; heartbeat falls back to host timezone (or `activeHours.timezone` if set).
- Cron without `--tz` uses gateway host timezone.
- Heartbeat `activeHours` uses configured timezone resolution (`user`, `local`, or explicit IANA tz).
- ISO timestamps without timezone are treated as UTC for cron `at` schedules.
Common signatures:
- Jobs run at the wrong wall-clock time after host timezone changes.
- Heartbeat always skipped during your daytime because `activeHours.timezone` is wrong.
Related:
- [/automation/cron-jobs](/automation/cron-jobs)
- [/gateway/heartbeat](/gateway/heartbeat)
- [/automation/cron-vs-heartbeat](/automation/cron-vs-heartbeat)
- [/concepts/timezone](/concepts/timezone)

View File

@@ -337,4 +337,4 @@ Prefer `chat_guid` for stable routing:
- OpenClaw auto-hides known-broken actions based on the BlueBubbles server's macOS version. If edit still appears on macOS 26 (Tahoe), disable it manually with `channels.bluebubbles.actions.edit=false`.
- For status/health info: `openclaw status --all` or `openclaw status --deep`.
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/plugins) guide.
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/plugin) guide.

View File

@@ -62,6 +62,28 @@ Disable with:
- Automation permission when sending.
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
## Troubleshooting macOS Privacy and Security TCC
If sending/receiving fails (for example, `imsg rpc` exits non-zero, times out, or the gateway appears to hang), a common cause is a macOS permission prompt that was never approved.
macOS grants TCC permissions per app/process context. Approve prompts in the same context that runs `imsg` (for example, Terminal/iTerm, a LaunchAgent session, or an SSH-launched process).
Checklist:
- **Full Disk Access**: allow access for the process running OpenClaw (and any shell/SSH wrapper that executes `imsg`). This is required to read the Messages database (`chat.db`).
- **Automation → Messages**: allow the process running OpenClaw (and/or your terminal) to control **Messages.app** for outbound sends.
- **`imsg` CLI health**: verify `imsg` is installed and supports RPC (`imsg rpc --help`).
Tip: If OpenClaw is running headless (LaunchAgent/systemd/SSH) the macOS prompt can be easy to miss. Run a one-time interactive command in a GUI terminal to force the prompt, then retry:
```bash
imsg chats --limit 1
# or
imsg send <handle> "test"
```
Related macOS folder permissions (Desktop/Documents/Downloads): [/platforms/mac/permissions](/platforms/mac/permissions).
## Setup (fast path)
1. Ensure Messages is signed in on this Mac.
@@ -81,7 +103,7 @@ If you want the bot to send from a **separate iMessage identity** (and keep your
6. Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry. See [Troubleshooting macOS Privacy and Security TCC](#troubleshooting-macos-privacy-and-security-tcc).
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:

View File

@@ -202,6 +202,32 @@ Once verified, the bot can decrypt messages in encrypted rooms.
| Location | ✅ Supported (geo URI; altitude ignored) |
| Native commands | ✅ Supported |
## Troubleshooting
Run this ladder first:
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Then confirm DM pairing state if needed:
```bash
openclaw pairing list matrix
```
Common failures:
- Logged in but room messages ignored: room blocked by `groupPolicy` or room allowlist.
- DMs ignored: sender pending approval when `channels.matrix.dm.policy="pairing"`.
- Encrypted rooms fail: crypto support or encryption settings mismatch.
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
## Configuration reference (Matrix)
Full configuration: [Configuration](/gateway/configuration)

View File

@@ -168,6 +168,32 @@ Config:
- Groups: `signal:group:<groupId>`.
- Usernames: `username:<name>` (if supported by your Signal account).
## Troubleshooting
Run this ladder first:
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Then confirm DM pairing state if needed:
```bash
openclaw pairing list signal
```
Common failures:
- Daemon reachable but no replies: verify account/daemon settings (`httpUrl`, `account`) and receive mode.
- DMs ignored: sender is pending pairing approval.
- Group messages ignored: group sender/mention gating blocks delivery.
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
## Configuration reference (Signal)
Full configuration: [Configuration](/gateway/configuration)

View File

@@ -537,6 +537,32 @@ Slack tool actions can be gated with `channels.slack.actions.*`:
scopes you expect (`chat:write`, `reactions:write`, `pins:write`,
`files:write`) or those operations will fail.
## Troubleshooting
Run this ladder first:
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Then confirm DM pairing state if needed:
```bash
openclaw pairing list slack
```
Common failures:
- Connected but no channel replies: channel blocked by `groupPolicy` or not in `channels.slack.channels` allowlist.
- DMs ignored: sender not approved when `channels.slack.dm.policy="pairing"`.
- API errors (`missing_scope`, `not_in_channel`, auth failures): bot/app tokens or Slack scopes are incomplete.
For triage flow: [/channels/troubleshooting](/channels/troubleshooting).
## Notes
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.

View File

@@ -1,29 +1,116 @@
---
summary: "Channel-specific troubleshooting shortcuts (Discord/Telegram/WhatsApp)"
summary: "Fast channel level troubleshooting with per channel failure signatures and fixes"
read_when:
- A channel connects but messages dont flow
- Investigating channel misconfiguration (intents, permissions, privacy mode)
- Channel transport says connected but replies fail
- You need channel specific checks before deep provider docs
title: "Channel Troubleshooting"
---
# Channel troubleshooting
Start with:
Use this page when a channel connects but behavior is wrong.
## Command ladder
Run these in order first:
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
`channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership).
Healthy baseline:
## Channels
- `Runtime: running`
- `RPC probe: ok`
- Channel probe shows connected/ready
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
## WhatsApp
## Telegram quick fixes
### WhatsApp failure signatures
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
| Symptom | Fastest check | Fix |
| ------------------------------- | --------------------------------------------------- | ------------------------------------------------------- |
| Connected but no DM replies | `openclaw pairing list whatsapp` | Approve sender or switch DM policy/allowlist. |
| Group messages ignored | Check `requireMention` + mention patterns in config | Mention the bot or relax mention policy for that group. |
| Random disconnect/relogin loops | `openclaw channels status --probe` + logs | Re-login and verify credentials directory is healthy. |
Full troubleshooting: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
## Telegram
### Telegram failure signatures
| Symptom | Fastest check | Fix |
| --------------------------------- | ----------------------------------------------- | --------------------------------------------------------- |
| `/start` but no usable reply flow | `openclaw pairing list telegram` | Approve pairing or change DM policy. |
| Bot online but group stays silent | Verify mention requirement and bot privacy mode | Disable privacy mode for group visibility or mention bot. |
| Send failures with network errors | Inspect logs for Telegram API call failures | Fix DNS/IPv6/proxy routing to `api.telegram.org`. |
Full troubleshooting: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
## Discord
### Discord failure signatures
| Symptom | Fastest check | Fix |
| ------------------------------- | ----------------------------------- | --------------------------------------------------------- |
| Bot online but no guild replies | `openclaw channels status --probe` | Allow guild/channel and verify message content intent. |
| Group messages ignored | Check logs for mention gating drops | Mention bot or set guild/channel `requireMention: false`. |
| DM replies missing | `openclaw pairing list discord` | Approve DM pairing or adjust DM policy. |
Full troubleshooting: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
## Slack
### Slack failure signatures
| Symptom | Fastest check | Fix |
| -------------------------------------- | ----------------------------------------- | ------------------------------------------------- |
| Socket mode connected but no responses | `openclaw channels status --probe` | Verify app token + bot token and required scopes. |
| DMs blocked | `openclaw pairing list slack` | Approve pairing or relax DM policy. |
| Channel message ignored | Check `groupPolicy` and channel allowlist | Allow the channel or switch policy to `open`. |
Full troubleshooting: [/channels/slack#troubleshooting](/channels/slack#troubleshooting)
## iMessage and BlueBubbles
### iMessage and BlueBubbles failure signatures
| Symptom | Fastest check | Fix |
| -------------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------- |
| No inbound events | Verify webhook/server reachability and app permissions | Fix webhook URL or BlueBubbles server state. |
| Can send but no receive on macOS | Check macOS privacy permissions for Messages automation | Re-grant TCC permissions and restart channel process. |
| DM sender blocked | `openclaw pairing list imessage` or `openclaw pairing list bluebubbles` | Approve pairing or update allowlist. |
Full troubleshooting:
- [/channels/imessage#troubleshooting-macos-privacy-and-security-tcc](/channels/imessage#troubleshooting-macos-privacy-and-security-tcc)
- [/channels/bluebubbles#troubleshooting](/channels/bluebubbles#troubleshooting)
## Signal
### Signal failure signatures
| Symptom | Fastest check | Fix |
| ------------------------------- | ------------------------------------------ | -------------------------------------------------------- |
| Daemon reachable but bot silent | `openclaw channels status --probe` | Verify `signal-cli` daemon URL/account and receive mode. |
| DM blocked | `openclaw pairing list signal` | Approve sender or adjust DM policy. |
| Group replies do not trigger | Check group allowlist and mention patterns | Add sender/group or loosen gating. |
Full troubleshooting: [/channels/signal#troubleshooting](/channels/signal#troubleshooting)
## Matrix
### Matrix failure signatures
| Symptom | Fastest check | Fix |
| ----------------------------------- | -------------------------------------------- | ----------------------------------------------- |
| Logged in but ignores room messages | `openclaw channels status --probe` | Check `groupPolicy` and room allowlist. |
| DMs do not process | `openclaw pairing list matrix` | Approve sender or adjust DM policy. |
| Encrypted rooms fail | Verify crypto module and encryption settings | Enable encryption support and rejoin/sync room. |
Full troubleshooting: [/channels/matrix#troubleshooting](/channels/matrix#troubleshooting)

View File

@@ -14,7 +14,7 @@ Provided by the active memory plugin (default: `memory-core`; set `plugins.slots
Related:
- Memory concept: [Memory](/concepts/memory)
- Plugins: [Plugins](/plugins)
- Plugins: [Plugins](/plugin)
## Examples

View File

@@ -88,7 +88,8 @@ Defaults:
1. `local` if a `memorySearch.local.modelPath` is configured and the file exists.
2. `openai` if an OpenAI key can be resolved.
3. `gemini` if a Gemini key can be resolved.
4. Otherwise memory search stays disabled until configured.
4. `voyage` if a Voyage key can be resolved.
5. Otherwise memory search stays disabled until configured.
- Local mode uses node-llama-cpp and may require `pnpm approve-builds`.
- Uses sqlite-vec (when available) to accelerate vector search inside SQLite.
@@ -96,7 +97,8 @@ Remote embeddings **require** an API key for the embedding provider. OpenClaw
resolves keys from auth profiles, `models.providers.*.apiKey`, or environment
variables. Codex OAuth only covers chat/completions and does **not** satisfy
embeddings for memory search. For Gemini, use `GEMINI_API_KEY` or
`models.providers.google.apiKey`. When using a custom OpenAI-compatible endpoint,
`models.providers.google.apiKey`. For Voyage, use `VOYAGE_API_KEY` or
`models.providers.voyage.apiKey`. When using a custom OpenAI-compatible endpoint,
set `memorySearch.remote.apiKey` (and optional `memorySearch.remote.headers`).
### QMD backend (experimental)
@@ -109,7 +111,7 @@ out to QMD for retrieval. Key points:
**Prereqs**
- Disabled by default. Opt in per-config (`memory.backend = "qmd"`).
- Install the QMD CLI separately (`bun install -g github.com/tobi/qmd` or grab
- Install the QMD CLI separately (`bun install -g https://github.com/tobi/qmd` or grab
a release) and make sure the `qmd` binary is on the gateways `PATH`.
- QMD needs an SQLite build that allows extensions (`brew install sqlite` on
macOS).

View File

@@ -98,6 +98,10 @@
"source": "/opencode",
"destination": "/providers/opencode"
},
{
"source": "/qianfan",
"destination": "/providers/qianfan"
},
{
"source": "/mattermost",
"destination": "/channels/mattermost"
@@ -660,7 +664,7 @@
},
{
"source": "/troubleshooting",
"destination": "/gateway/troubleshooting"
"destination": "/help/troubleshooting"
},
{
"source": "/web/tui",
@@ -962,6 +966,7 @@
"hooks/soul-evil",
"automation/cron-jobs",
"automation/cron-vs-heartbeat",
"automation/troubleshooting",
"automation/webhook",
"automation/gmail-pubsub",
"automation/poll",
@@ -972,6 +977,7 @@
"group": "Media and devices",
"pages": [
"nodes/index",
"nodes/troubleshooting",
"nodes/images",
"nodes/audio",
"nodes/camera",
@@ -1006,7 +1012,8 @@
"providers/opencode",
"providers/glm",
"providers/zai",
"providers/synthetic"
"providers/synthetic",
"providers/qianfan"
]
}
]

View File

@@ -1453,7 +1453,7 @@ working directory). The path must exist to be used.
### `agents.defaults.skipBootstrap`
Disables automatic creation of the workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, and `BOOTSTRAP.md`).
Disables automatic creation of the workspace bootstrap files (`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, and `BOOTSTRAP.md`).
Use this for pre-seeded deployments where your workspace files come from a repo.

View File

@@ -13,6 +13,8 @@ title: "Heartbeat"
Heartbeat runs **periodic agent turns** in the main session so the model can
surface anything that needs attention without spamming you.
Troubleshooting: [/automation/troubleshooting](/automation/troubleshooting)
## Quick start (beginner)
1. Leave heartbeats enabled (default is `30m`, or `1h` for Anthropic OAuth/setup-token) or set your own cadence.

View File

@@ -23,7 +23,7 @@ misconfiguration safety), under explicit assumptions.
## Where the models live
Models are maintained in a separate repo: [vignesh07/openclaw-formal-models](https://github.com/vignesh07/openclaw-formal-models).
Models are maintained in a separate repo: [vignesh07/clawdbot-formal-models](https://github.com/vignesh07/clawdbot-formal-models).
## Important caveats
@@ -41,8 +41,8 @@ Today, results are reproduced by cloning the models repo locally and running TLC
Getting started:
```bash
git clone https://github.com/vignesh07/openclaw-formal-models
cd openclaw-formal-models
git clone https://github.com/vignesh07/clawdbot-formal-models
cd clawdbot-formal-models
# Java 11+ required (TLC runs on the JVM).
# The repo vendors a pinned `tla2tools.jar` (TLA+ tools) and provides `bin/tlc` + Make targets.

File diff suppressed because it is too large Load Diff

View File

@@ -3,212 +3,396 @@ summary: "How to submit a high signal PR"
title: "Submitting a PR"
---
# Submitting a PR
Good PRs make it easy for reviewers to understand intent, verify behavior, and land changes safely. This guide focuses on high-signal, low-noise submissions that work well with both human review and LLM-assisted review.
Good PRs are easy to review: reviewers should quickly know the intent, verify behavior, and land changes safely. This guide covers concise, high-signal submissions for human and LLM review.
## What makes a good PR
- [ ] Clear intent: explain the problem, why it matters, and what the change does.
- [ ] Tight scope: keep changes focused and avoid drive-by refactors.
- [ ] Behavior summary: call out user-visible changes, config changes, and defaults.
- [ ] Tests: list what ran, what was skipped, and why.
- [ ] Evidence: include logs, screenshots, or short recordings for UI or workflows.
- [ ] Code word: include “lobster-biscuit” somewhere in the PR description to confirm you read this guide.
- [ ] Baseline checks: run the relevant `pnpm` commands for this repo and fix failures before opening the PR.
- [ ] Due diligence: search the codebase for existing functionality and check GitHub for related issues or prior fixes.
- [ ] Grounded in reality: claims should be backed by evidence, reproduction, or direct observation.
- [ ] Title guidance: use a verb + scope + outcome (for example `Docs: add PR and issue templates`).
- [ ] Explain the problem, why it matters, and the change.
- [ ] Keep changes focused. Avoid broad refactors.
- [ ] Summarize user-visible/config/default changes.
- [ ] List test coverage, skips, and reasons.
- [ ] Add evidence: logs, screenshots, or recordings (UI/UX).
- [ ] Code word: put “lobster-biscuit” in the PR description if you read this guide.
- [ ] Run/fix relevant `pnpm` commands before creating PR.
- [ ] Search codebase and GitHub for related functionality/issues/fixes.
- [ ] Base claims on evidence or observation.
- [ ] Good title: verb + scope + outcome (e.g., `Docs: add PR and issue templates`).
Guideline: concision > grammar. Be terse if it makes review faster.
Be concise; concise review > grammar. Omit any non-applicable sections.
Baseline validation commands (run as appropriate for the change, and fix failures before submitting):
### Baseline validation commands (run/fix failures for your change)
- `pnpm lint`
- `pnpm check`
- `pnpm build`
- `pnpm test`
- If you touch protocol code: `pnpm protocol:check`
- Protocol changes: `pnpm protocol:check`
## Progressive disclosure
Use a short top section, then deeper details as needed.
- Top: summary/intent
- Next: changes/risks
- Next: test/verification
- Last: implementation/evidence
1. Summary and intent
2. Behavior changes and risks
3. Tests and verification
4. Implementation details and evidence
## Common PR types: specifics
This keeps review fast while preserving deep context for anyone who needs it.
## Common PR types and expectations
- [ ] Fix: include clear repro, root cause summary, and verification steps.
- [ ] Feature: include use cases, behavior changes, and screenshots or demos when UI is involved.
- [ ] Refactor: explicitly state “no behavior change” and list what moved or was simplified.
- [ ] Chore/Maintenance: note why it matters (build time, CI stability, dependency hygiene).
- [ ] Docs: include before/after context and link to the updated page. Run `pnpm format`.
- [ ] Test: explain the gap it covers and how it prevents regressions.
- [ ] Perf: include baseline and after metrics, plus how they were measured.
- [ ] UX/UI: include screenshots or short recordings and any accessibility impact.
- [ ] Infra/Build: call out affected environments and how to validate.
- [ ] Security: include threat or risk summary, repro steps, and verification plan. Avoid sensitive data in public logs.
- [ ] Security: keep reports grounded in reality; avoid speculative claims.
- [ ] Fix: Add repro, root cause, verification.
- [ ] Feature: Add use cases, behavior/demos/screenshots (UI).
- [ ] Refactor: State "no behavior change", list what moved/simplified.
- [ ] Chore: State why (e.g., build time, CI, dependencies).
- [ ] Docs: Before/after context, link updated page, run `pnpm format`.
- [ ] Test: What gap is covered; how it prevents regressions.
- [ ] Perf: Add before/after metrics, and how measured.
- [ ] UX/UI: Screenshots/video, note accessibility impact.
- [ ] Infra/Build: Environments/validation.
- [ ] Security: Summarize risk, repro, verification, no sensitive data. Grounded claims only.
## Checklist
- [ ] Problem and intent are clear
- [ ] Scope is focused
- [ ] Behavior changes are listed
- [ ] Tests are listed with results
- [ ] Evidence is attached when needed
- [ ] No secrets or private data
- [ ] Grounded in reality: no guesswork or invented context.
- [ ] Clear problem/intent
- [ ] Focused scope
- [ ] List behavior changes
- [ ] List and result of tests
- [ ] Manual test steps (when applicable)
- [ ] No secrets/private data
- [ ] Evidence-based
## Template
## General PR Template
```md
## Summary
#### Summary
## Behavior Changes
#### Behavior Changes
## Codebase and GitHub Search
#### Codebase and GitHub Search
## Tests
#### Tests
## Evidence
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort (self-reported):
- Agent notes (optional, cite evidence):
```
## Templates by PR type
## PR Type templates (replace with your type)
### Fix
```md
## Summary
#### Summary
## Repro Steps
#### Repro Steps
## Root Cause
#### Root Cause
## Behavior Changes
#### Behavior Changes
## Tests
#### Tests
## Evidence
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Feature
```md
## Summary
#### Summary
## Use Cases
#### Use Cases
## Behavior Changes
#### Behavior Changes
## Existing Functionality Check
#### Existing Functionality Check
I searched the codebase for existing functionality before implementing this.
- [ ] I searched the codebase for existing functionality.
Searches performed (1-3 bullets):
-
-
## Tests
#### Tests
## Evidence
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Refactor
```md
## Summary
#### Summary
## Scope
#### Scope
## No Behavior Change Statement
#### No Behavior Change Statement
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Chore/Maintenance
```md
## Summary
#### Summary
## Why This Matters
#### Why This Matters
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Docs
```md
## Summary
#### Summary
## Pages Updated
#### Pages Updated
## Screenshots or Before/After
#### Before/After
## Formatting
#### Formatting
pnpm format
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Test
```md
## Summary
#### Summary
## Gap Covered
#### Gap Covered
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Perf
```md
## Summary
#### Summary
## Baseline
#### Baseline
## After
#### After
## Measurement Method
#### Measurement Method
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### UX/UI
```md
## Summary
#### Summary
## Screenshots or Video
#### Screenshots or Video
## Accessibility Impact
#### Accessibility Impact
## Tests
#### Tests
#### Manual Testing
### Prerequisites
-
### Steps
1.
2. **Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Infra/Build
```md
## Summary
#### Summary
## Environments Affected
#### Environments Affected
## Validation Steps
#### Validation Steps
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```
### Security
```md
## Summary
#### Summary
## Risk Summary
#### Risk Summary
## Repro Steps
#### Repro Steps
## Mitigation or Fix
#### Mitigation or Fix
## Verification
#### Verification
## Tests
#### Tests
#### Manual Testing (omit if N/A)
### Prerequisites
-
### Steps
1.
2.
#### Evidence (omit if N/A)
**Sign-Off**
- Models used:
- Submitter effort:
- Agent notes:
```

View File

@@ -1,165 +1,152 @@
---
summary: "How to file high signal issues and bug reports"
summary: "Filing high-signal issues and bug reports"
title: "Submitting an Issue"
---
# Submitting an Issue
## Submitting an Issue
Good issues make it easy to reproduce, diagnose, and fix problems quickly. This guide covers what to include for bugs, regressions, and feature gaps.
Clear, concise issues speed up diagnosis and fixes. Include the following for bugs, regressions, or feature gaps:
## What makes a good issue
### What to include
- [ ] Clear title: include the area and the symptom.
- [ ] Repro steps: minimal steps that consistently reproduce the issue.
- [ ] Expected vs actual: what you thought would happen and what did.
- [ ] Impact: who is affected and how severe the problem is.
- [ ] Environment: OS, runtime, versions, and relevant config.
- [ ] Evidence: logs, screenshots, or recordings (redacted; prefer non-PII data).
- [ ] Scope: note if it is new, regression, or long-standing.
- [ ] Code word: include “lobster-biscuit” somewhere in the issue description to confirm you read this guide.
- [ ] Due diligence: search the codebase for existing functionality and check GitHub to see if the issue is already filed or fixed.
- [ ] I searched for existing and recently closed issues/PRs.
- [ ] For security reports: confirmed it has not already been fixed or addressed recently.
- [ ] Grounded in reality: claims should be backed by evidence, reproduction, or direct observation.
- [ ] Title: area & symptom
- [ ] Minimal repro steps
- [ ] Expected vs actual
- [ ] Impact & severity
- [ ] Environment: OS, runtime, versions, config
- [ ] Evidence: redacted logs, screenshots (non-PII)
- [ ] Scope: new, regression, or longstanding
- [ ] Code word: lobster-biscuit in your issue
- [ ] Searched codebase & GitHub for existing issue
- [ ] Confirmed not recently fixed/addressed (esp. security)
- [ ] Claims backed by evidence or repro
Guideline: concision > grammar. Be terse if it makes review faster.
Be brief. Terseness > perfect grammar.
Baseline validation commands (run as appropriate for the change, and fix failures before submitting a PR):
Validation (run/fix before PR):
- `pnpm lint`
- `pnpm check`
- `pnpm build`
- `pnpm test`
- If you touch protocol code: `pnpm protocol:check`
- If protocol code: `pnpm protocol:check`
## Templates
### Templates
### Bug report
#### Bug report
```md
## Bug report checklist
- [ ] Minimal repro steps
- [ ] Minimal repro
- [ ] Expected vs actual
- [ ] Versions and environment
- [ ] Affected channels and where it does not reproduce
- [ ] Logs or screenshots
- [ ] Evidence is redacted and non-PII where possible
- [ ] Impact and severity
- [ ] Any known workarounds
- [ ] Environment
- [ ] Affected channels, where not seen
- [ ] Logs/screenshots (redacted)
- [ ] Impact/severity
- [ ] Workarounds
## Summary
### Summary
## Repro Steps
### Repro Steps
## Expected
### Expected
## Actual
### Actual
## Environment
### Environment
## Logs or Evidence
### Logs/Evidence
## Impact
### Impact
## Workarounds
### Workarounds
```
### Security issue
#### Security issue
```md
## Summary
### Summary
## Impact
### Impact
## Affected Versions
### Versions
## Repro Steps (if safe to share)
### Repro Steps (safe to share)
## Mitigation or Workaround
### Mitigation/workaround
## Evidence (redacted)
### Evidence (redacted)
```
Security note: avoid posting secrets or exploit details in public issues. If the report is sensitive, keep repro details minimal and ask for a private disclosure path.
_Avoid secrets/exploit details in public. For sensitive issues, minimize detail and request private disclosure._
### Regression report
#### Regression report
```md
## Summary
### Summary
## Last Known Good
### Last Known Good
## First Known Bad
### First Known Bad
## Repro Steps
### Repro Steps
## Expected
### Expected
## Actual
### Actual
## Environment
### Environment
## Logs or Evidence
### Logs/Evidence
## Impact
### Impact
```
### Feature request
#### Feature request
```md
## Summary
### Summary
## Problem
### Problem
## Proposed Solution
### Proposed Solution
## Alternatives Considered
### Alternatives
## Impact
### Impact
## Evidence or Examples
### Evidence/examples
```
### Enhancement request
#### Enhancement
```md
## Summary
### Summary
## Current Behavior
### Current vs Desired Behavior
## Desired Behavior
### Rationale
## Why This Matters
### Alternatives
## Alternatives Considered
## Evidence or Examples
### Evidence/examples
```
### Investigation request
#### Investigation
```md
## Summary
### Summary
## Symptoms
### Symptoms
## What Was Tried
### What Was Tried
## Environment
### Environment
## Logs or Evidence
### Logs/Evidence
## Impact
### Impact
```
## If you are submitting a fix PR
### Submitting a fix PR
Creating a separate issue first is optional. If you skip it, include the relevant details in the PR description.
- Keep the PR focused on the issue.
- Include the issue number in the PR description.
- Add tests when possible, or explain why they are not feasible.
- Note any behavior changes and risks.
- Include redacted logs, screenshots, or videos that validate the fix.
- Run relevant `pnpm` validation commands and report results when appropriate.
Issue before PR is optional. Include details in PR if skipping. Keep the PR focused, note issue number, add tests or explain absence, document behavior changes/risks, include redacted logs/screenshots as proof, and run proper validation before submitting.

View File

@@ -1,98 +1,265 @@
---
summary: "Troubleshooting hub: symptoms → checks → fixes"
summary: "Symptom first troubleshooting hub for OpenClaw"
read_when:
- You see an error and want the fix path
- The installer says “success” but the CLI doesnt work
- OpenClaw is not working and you need the fastest path to a fix
- You want a triage flow before diving into deep runbooks
title: "Troubleshooting"
---
# Troubleshooting
If you only have 2 minutes, use this page as a triage front door.
## First 60 seconds
Run these in order:
Run this exact ladder in order:
```bash
openclaw status
openclaw status --all
openclaw gateway probe
openclaw logs --follow
openclaw gateway status
openclaw doctor
openclaw channels status --probe
openclaw logs --follow
```
If the gateway is reachable, deep probes:
Good output in one line:
```bash
openclaw status --deep
- `openclaw status` → shows configured channels and no obvious auth errors.
- `openclaw status --all` → full report is present and shareable.
- `openclaw gateway probe` → expected gateway target is reachable.
- `openclaw gateway status``Runtime: running` and `RPC probe: ok`.
- `openclaw doctor` → no blocking config/service errors.
- `openclaw channels status --probe` → channels report `connected` or `ready`.
- `openclaw logs --follow` → steady activity, no repeating fatal errors.
## Decision tree
```mermaid
flowchart TD
A[OpenClaw is not working] --> B{What breaks first}
B --> C[No replies]
B --> D[Dashboard or Control UI will not connect]
B --> E[Gateway will not start or service not running]
B --> F[Channel connects but messages do not flow]
B --> G[Cron or heartbeat did not fire or did not deliver]
B --> H[Node is paired but camera canvas screen exec fails]
B --> I[Browser tool fails]
C --> C1[/No replies section/]
D --> D1[/Control UI section/]
E --> E1[/Gateway section/]
F --> F1[/Channel flow section/]
G --> G1[/Automation section/]
H --> H1[/Node tools section/]
I --> I1[/Browser section/]
```
## Common “it broke” cases
<AccordionGroup>
<Accordion title="No replies">
```bash
openclaw status
openclaw gateway status
openclaw channels status --probe
openclaw pairing list <channel>
openclaw logs --follow
```
### `openclaw: command not found`
Good output looks like:
Almost always a Node/npm PATH issue. Start here:
- `Runtime: running`
- `RPC probe: ok`
- Your channel shows connected/ready in `channels status --probe`
- Sender appears approved (or DM policy is open/allowlist)
- [Install (Node/npm PATH sanity)](/install#nodejs--npm-path-sanity)
Common log signatures:
### Installer fails (or you need full logs)
- `drop guild message (mention required` → mention gating blocked the message in Discord.
- `pairing request` → sender is unapproved and waiting for DM pairing approval.
- `blocked` / `allowlist` in channel logs → sender, room, or group is filtered.
Re-run the installer in verbose mode to see the full trace and npm output:
Deep pages:
```bash
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --verbose
```
- [/gateway/troubleshooting#no-replies](/gateway/troubleshooting#no-replies)
- [/channels/troubleshooting](/channels/troubleshooting)
- [/start/pairing](/start/pairing)
For beta installs:
</Accordion>
```bash
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --beta --verbose
```
<Accordion title="Dashboard or Control UI will not connect">
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
You can also set `OPENCLAW_VERBOSE=1` instead of the flag.
Good output looks like:
### Gateway “unauthorized”, cant connect, or keeps reconnecting
- `Dashboard: http://...` is shown in `openclaw gateway status`
- `RPC probe: ok`
- No auth loop in logs
- [Gateway troubleshooting](/gateway/troubleshooting)
- [Gateway authentication](/gateway/authentication)
Common log signatures:
### Control UI fails on HTTP (device identity required)
- `device identity required` → HTTP/non-secure context cannot complete device auth.
- `unauthorized` / reconnect loop → wrong token/password or auth mode mismatch.
- `gateway connect failed:` → UI is targeting the wrong URL/port or unreachable gateway.
- [Gateway troubleshooting](/gateway/troubleshooting)
- [Control UI](/web/control-ui#insecure-http)
Deep pages:
### `docs.openclaw.ai` shows an SSL error (Comcast/Xfinity)
- [/gateway/troubleshooting#dashboard-control-ui-connectivity](/gateway/troubleshooting#dashboard-control-ui-connectivity)
- [/web/control-ui](/web/control-ui)
- [/gateway/authentication](/gateway/authentication)
Some Comcast/Xfinity connections block `docs.openclaw.ai` via Xfinity Advanced Security.
Disable Advanced Security or add `docs.openclaw.ai` to the allowlist, then retry.
</Accordion>
- Xfinity Advanced Security help: [https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security](https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security)
- Quick sanity checks: try a mobile hotspot or VPN to confirm its ISP-level filtering
<Accordion title="Gateway will not start or service installed but not running">
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
### Service says running, but RPC probe fails
Good output looks like:
- [Gateway troubleshooting](/gateway/troubleshooting)
- [Background process / service](/gateway/background-process)
- `Service: ... (loaded)`
- `Runtime: running`
- `RPC probe: ok`
### Model/auth failures (rate limit, billing, “all models failed”)
Common log signatures:
- [Models](/cli/models)
- [OAuth / auth concepts](/concepts/oauth)
- `Gateway start blocked: set gateway.mode=local` → gateway mode is unset/remote.
- `refusing to bind gateway ... without auth` → non-loopback bind without token/password.
- `another gateway instance is already listening` or `EADDRINUSE` → port already taken.
### `/model` says `model not allowed`
Deep pages:
This usually means `agents.defaults.models` is configured as an allowlist. When its non-empty,
only those provider/model keys can be selected.
- [/gateway/troubleshooting#gateway-service-not-running](/gateway/troubleshooting#gateway-service-not-running)
- [/gateway/background-process](/gateway/background-process)
- [/gateway/configuration](/gateway/configuration)
- Check the allowlist: `openclaw config get agents.defaults.models`
- Add the model you want (or clear the allowlist) and retry `/model`
- Use `/models` to browse the allowed providers/models
</Accordion>
### When filing an issue
<Accordion title="Channel connects but messages do not flow">
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Paste a safe report:
Good output looks like:
```bash
openclaw status --all
```
- Channel transport is connected.
- Pairing/allowlist checks pass.
- Mentions are detected where required.
If you can, include the relevant log tail from `openclaw logs --follow`.
Common log signatures:
- `mention required` → group mention gating blocked processing.
- `pairing` / `pending` → DM sender is not approved yet.
- `not_in_channel`, `missing_scope`, `Forbidden`, `401/403` → channel permission token issue.
Deep pages:
- [/gateway/troubleshooting#channel-connected-messages-not-flowing](/gateway/troubleshooting#channel-connected-messages-not-flowing)
- [/channels/troubleshooting](/channels/troubleshooting)
</Accordion>
<Accordion title="Cron or heartbeat did not fire or did not deliver">
```bash
openclaw status
openclaw gateway status
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw logs --follow
```
Good output looks like:
- `cron.status` shows enabled with a next wake.
- `cron runs` shows recent `ok` entries.
- Heartbeat is enabled and not outside active hours.
Common log signatures:
- `cron: scheduler disabled; jobs will not run automatically` → cron is disabled.
- `heartbeat skipped` with `reason=quiet-hours` → outside configured active hours.
- `requests-in-flight` → main lane busy; heartbeat wake was deferred.
- `unknown accountId` → heartbeat delivery target account does not exist.
Deep pages:
- [/gateway/troubleshooting#cron-and-heartbeat-delivery](/gateway/troubleshooting#cron-and-heartbeat-delivery)
- [/automation/troubleshooting](/automation/troubleshooting)
- [/gateway/heartbeat](/gateway/heartbeat)
</Accordion>
<Accordion title="Node is paired but tool fails camera canvas screen exec">
```bash
openclaw status
openclaw gateway status
openclaw nodes status
openclaw nodes describe --node <idOrNameOrIp>
openclaw logs --follow
```
Good output looks like:
- Node is listed as connected and paired for role `node`.
- Capability exists for the command you are invoking.
- Permission state is granted for the tool.
Common log signatures:
- `NODE_BACKGROUND_UNAVAILABLE` → bring node app to foreground.
- `*_PERMISSION_REQUIRED` → OS permission was denied/missing.
- `SYSTEM_RUN_DENIED: approval required` → exec approval is pending.
- `SYSTEM_RUN_DENIED: allowlist miss` → command not on exec allowlist.
Deep pages:
- [/gateway/troubleshooting#node-paired-tool-fails](/gateway/troubleshooting#node-paired-tool-fails)
- [/nodes/troubleshooting](/nodes/troubleshooting)
- [/tools/exec-approvals](/tools/exec-approvals)
</Accordion>
<Accordion title="Browser tool fails">
```bash
openclaw status
openclaw gateway status
openclaw browser status
openclaw logs --follow
openclaw doctor
```
Good output looks like:
- Browser status shows `running: true` and a chosen browser/profile.
- `openclaw` profile starts or `chrome` relay has an attached tab.
Common log signatures:
- `Failed to start Chrome CDP on port` → local browser launch failed.
- `browser.executablePath not found` → configured binary path is wrong.
- `Chrome extension relay is running, but no tab is connected` → extension not attached.
- `Browser attachOnly is enabled ... not reachable` → attach-only profile has no live CDP target.
Deep pages:
- [/gateway/troubleshooting#browser-tool-fails](/gateway/troubleshooting#browser-tool-fails)
- [/tools/browser-linux-troubleshooting](/tools/browser-linux-troubleshooting)
- [/tools/chrome-extension](/tools/chrome-extension)
</Accordion>
</AccordionGroup>

View File

@@ -11,11 +11,11 @@ title: "Installer Internals"
OpenClaw ships three installer scripts, served from `openclaw.ai`.
| Script | Platform | What it does |
| ----------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| [`install.sh`](#install-sh) | macOS / Linux / WSL | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
| [`install-cli.sh`](#install-cli-sh) | macOS / Linux / WSL | Installs Node + OpenClaw into a local prefix (`~/.openclaw`). No root required. |
| [`install.ps1`](#install-ps1) | Windows (PowerShell) | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
| Script | Platform | What it does |
| ---------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| [`install.sh`](#installsh) | macOS / Linux / WSL | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
| [`install-cli.sh`](#install-clish) | macOS / Linux / WSL | Installs Node + OpenClaw into a local prefix (`~/.openclaw`). No root required. |
| [`install.ps1`](#installps1) | Windows (PowerShell) | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
## Quick commands
@@ -64,7 +64,7 @@ If install succeeds but `openclaw` is not found in a new terminal, see [Node.js
Recommended for most interactive installs on macOS/Linux/WSL.
</Tip>
### Flow
### Flow (install.sh)
<Steps>
<Step title="Detect OS">
@@ -98,7 +98,7 @@ If no TTY is available and no install method is set, it defaults to `npm` and wa
The script exits with code `2` for invalid method selection or invalid `--install-method` values.
### Examples
### Examples (install.sh)
<Tabs>
<Tab title="Default">
@@ -171,7 +171,7 @@ The script exits with code `2` for invalid method selection or invalid `--instal
Designed for environments where you want everything under a local prefix (default `~/.openclaw`) and no system Node dependency.
</Info>
### Flow
### Flow (install-cli.sh)
<Steps>
<Step title="Install local Node runtime">
@@ -185,7 +185,7 @@ Designed for environments where you want everything under a local prefix (defaul
</Step>
</Steps>
### Examples
### Examples (install-cli.sh)
<Tabs>
<Tab title="Default">
@@ -245,7 +245,7 @@ Designed for environments where you want everything under a local prefix (defaul
## install.ps1
### Flow
### Flow (install.ps1)
<Steps>
<Step title="Ensure PowerShell + Windows environment">
@@ -263,7 +263,7 @@ Designed for environments where you want everything under a local prefix (defaul
</Step>
</Steps>
### Examples
### Examples (install.ps1)
<Tabs>
<Tab title="Default">

View File

@@ -19,6 +19,7 @@ Notes:
- Nodes are **peripherals**, not gateways. They dont run the gateway service.
- Telegram/WhatsApp/etc. messages land on the **gateway**, not on nodes.
- Troubleshooting runbook: [/nodes/troubleshooting](/nodes/troubleshooting)
## Pairing + status

View File

@@ -0,0 +1,112 @@
---
summary: "Troubleshoot node pairing, foreground requirements, permissions, and tool failures"
read_when:
- Node is connected but camera/canvas/screen/exec tools fail
- You need the node pairing versus approvals mental model
title: "Node Troubleshooting"
---
# Node troubleshooting
Use this page when a node is visible in status but node tools fail.
## Command ladder
```bash
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```
Then run node specific checks:
```bash
openclaw nodes status
openclaw nodes describe --node <idOrNameOrIp>
openclaw approvals get --node <idOrNameOrIp>
```
Healthy signals:
- Node is connected and paired for role `node`.
- `nodes describe` includes the capability you are calling.
- Exec approvals show expected mode/allowlist.
## Foreground requirements
`canvas.*`, `camera.*`, and `screen.*` are foreground only on iOS/Android nodes.
Quick check and fix:
```bash
openclaw nodes describe --node <idOrNameOrIp>
openclaw nodes canvas snapshot --node <idOrNameOrIp>
openclaw logs --follow
```
If you see `NODE_BACKGROUND_UNAVAILABLE`, bring the node app to the foreground and retry.
## Permissions matrix
| Capability | iOS | Android | macOS node app | Typical failure code |
| ---------------------------- | --------------------------------------- | -------------------------------------------- | ----------------------------- | ------------------------------ |
| `camera.snap`, `camera.clip` | Camera (+ mic for clip audio) | Camera (+ mic for clip audio) | Camera (+ mic for clip audio) | `*_PERMISSION_REQUIRED` |
| `screen.record` | Screen Recording (+ mic optional) | Screen capture prompt (+ mic optional) | Screen Recording | `*_PERMISSION_REQUIRED` |
| `location.get` | While Using or Always (depends on mode) | Foreground/Background location based on mode | Location permission | `LOCATION_PERMISSION_REQUIRED` |
| `system.run` | n/a (node host path) | n/a (node host path) | Exec approvals required | `SYSTEM_RUN_DENIED` |
## Pairing versus approvals
These are different gates:
1. **Device pairing**: can this node connect to the gateway?
2. **Exec approvals**: can this node run a specific shell command?
Quick checks:
```bash
openclaw devices list
openclaw nodes status
openclaw approvals get --node <idOrNameOrIp>
openclaw approvals allowlist add --node <idOrNameOrIp> "/usr/bin/uname"
```
If pairing is missing, approve the node device first.
If pairing is fine but `system.run` fails, fix exec approvals/allowlist.
## Common node error codes
- `NODE_BACKGROUND_UNAVAILABLE` → app is backgrounded; bring it foreground.
- `CAMERA_DISABLED` → camera toggle disabled in node settings.
- `*_PERMISSION_REQUIRED` → OS permission missing/denied.
- `LOCATION_DISABLED` → location mode is off.
- `LOCATION_PERMISSION_REQUIRED` → requested location mode not granted.
- `LOCATION_BACKGROUND_UNAVAILABLE` → app is backgrounded but only While Using permission exists.
- `SYSTEM_RUN_DENIED: approval required` → exec request needs explicit approval.
- `SYSTEM_RUN_DENIED: allowlist miss` → command blocked by allowlist mode.
## Fast recovery loop
```bash
openclaw nodes status
openclaw nodes describe --node <idOrNameOrIp>
openclaw approvals get --node <idOrNameOrIp>
openclaw logs --follow
```
If still stuck:
- Re-approve device pairing.
- Re-open node app (foreground).
- Re-grant OS permissions.
- Recreate/adjust exec approval policy.
Related:
- [/nodes/index](/nodes/index)
- [/nodes/camera](/nodes/camera)
- [/nodes/location-command](/nodes/location-command)
- [/tools/exec-approvals](/tools/exec-approvals)
- [/gateway/pairing](/gateway/pairing)

View File

@@ -40,5 +40,11 @@ sudo tccutil reset ScreenCapture bot.molt.mac
sudo tccutil reset AppleEvents
```
## Files and folders permissions (Desktop/Documents/Downloads)
macOS may also gate Desktop, Documents, and Downloads for terminal/background processes. If file reads or directory listings hang, grant access to the same process context that performs file operations (for example Terminal/iTerm, LaunchAgent-launched app, or SSH process).
Workaround: move files into the OpenClaw workspace (`~/.openclaw/workspace`) if you want to avoid per-folder grants.
If you are testing permissions, always sign with a real certificate. Ad-hoc
builds are only acceptable for quick local runs where permissions do not matter.

View File

@@ -34,17 +34,17 @@ Notes:
# From repo root; set release IDs so Sparkle feed is enabled.
# APP_BUILD must be numeric + monotonic for Sparkle compare.
BUNDLE_ID=bot.molt.mac \
APP_VERSION=2026.2.4 \
APP_VERSION=2026.2.6 \
APP_BUILD="$(git rev-list --count HEAD)" \
BUILD_CONFIG=release \
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
scripts/package-mac-app.sh
# Zip for distribution (includes resource forks for Sparkle delta support)
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.2.4.zip
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.2.6.zip
# Optional: also build a styled DMG for humans (drag to /Applications)
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.4.dmg
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.6.dmg
# Recommended: build + notarize/staple zip + DMG
# First, create a keychain profile once:
@@ -52,14 +52,14 @@ scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.4.dmg
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
NOTARIZE=1 NOTARYTOOL_PROFILE=openclaw-notary \
BUNDLE_ID=bot.molt.mac \
APP_VERSION=2026.2.4 \
APP_VERSION=2026.2.6 \
APP_BUILD="$(git rev-list --count HEAD)" \
BUILD_CONFIG=release \
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
scripts/package-mac-dist.sh
# Optional: ship dSYM alongside the release
ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.2.4.dSYM.zip
ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.2.6.dSYM.zip
```
## Appcast entry
@@ -67,7 +67,7 @@ ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenCl
Use the release note generator so Sparkle renders formatted HTML notes:
```bash
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.2.4.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.2.6.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
```
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
@@ -75,7 +75,7 @@ Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when
## Publish & verify
- Upload `OpenClaw-2026.2.4.zip` (and `OpenClaw-2026.2.4.dSYM.zip`) to the GitHub release for tag `v2026.2.4`.
- Upload `OpenClaw-2026.2.6.zip` (and `OpenClaw-2026.2.6.dSYM.zip`) to the GitHub release for tag `v2026.2.6`.
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`.
- Sanity checks:
- `curl -I https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml` returns 200.

View File

@@ -50,6 +50,7 @@ See [Venice AI](/providers/venice).
- [MiniMax](/providers/minimax)
- [Venice (Venice AI, privacy-focused)](/providers/venice)
- [Ollama (local models)](/providers/ollama)
- [Qianfan](/providers/qianfan)
## Transcription providers

View File

@@ -46,6 +46,7 @@ See [Venice AI](/providers/venice).
- [MiniMax](/providers/minimax)
- [Venice (Venice AI)](/providers/venice)
- [Amazon Bedrock](/bedrock)
- [Qianfan](/providers/qianfan)
For the full provider catalog (xAI, Groq, Mistral, etc.) and advanced configuration,
see [Model providers](/concepts/model-providers).

38
docs/providers/qianfan.md Normal file
View File

@@ -0,0 +1,38 @@
---
summary: "Use Qianfan's unified API to access many models in OpenClaw"
read_when:
- You want a single API key for many LLMs
- You need Baidu Qianfan setup guidance
title: "Qianfan"
---
# Qianfan Provider Guide
Qianfan is Baidu's MaaS platform, provides a **unified API** that routes requests to many models behind a single
endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL.
## Prerequisites
1. A Baidu Cloud account with Qianfan API access
2. An API key from the Qianfan console
3. OpenClaw installed on your system
## Getting Your API Key
1. Visit the [Qianfan Console](https://console.bce.baidu.com/qianfan/ais/console/apiKey)
2. Create a new application or select an existing one
3. Generate an API key (format: `bce-v3/ALTAK-...`)
4. Copy the API key for use with OpenClaw
## CLI setup
```bash
openclaw onboard --auth-choice qianfan-api-key
```
## Related Documentation
- [OpenClaw Configuration](/configuration)
- [Model Providers](/concepts/model-providers)
- [Agent Setup](/agents)
- [Qianfan API Documentation](https://cloud.baidu.com/doc/qianfan-api/s/3m7of64lb)

View File

@@ -110,7 +110,6 @@ git commit -m "Add Clawd workspace"
- **OpenHue CLI** — Philips Hue lighting control for scenes and automations.
- **OpenAI Whisper** — Local speech-to-text for quick dictation and voicemail transcripts.
- **Gemini CLI** — Google Gemini models from the terminal for fast Q&A.
- **bird** — X/Twitter CLI to tweet, reply, read threads, and search without a browser.
- **agent-tools** — Utility toolkit for automations and helper scripts.
## Usage Notes

View File

@@ -66,7 +66,8 @@ Semantic memory search uses **embedding APIs** when configured for remote provid
- `memorySearch.provider = "openai"` → OpenAI embeddings
- `memorySearch.provider = "gemini"` → Gemini embeddings
- Optional fallback to OpenAI if local embeddings fail
- `memorySearch.provider = "voyage"` → Voyage embeddings
- Optional fallback to a remote provider if local embeddings fail
You can keep it local with `memorySearch.provider = "local"` (no API usage).

131
docs/reference/pipeline.md Normal file
View File

@@ -0,0 +1,131 @@
# Release Pipeline
This document describes openclaw's staged release pipeline for contributors and maintainers.
## Branch Strategy
```
dev/* ──────► develop ──────► alpha ──────► beta ──────► main
feature/* │ │ │ │
fix/* │ │ │ │
▼ ▼ ▼ ▼
Internal Alpha Beta Stable
testing testers testers release
```
### Branch Purposes
| Branch | Purpose | npm tag | Who uses it |
| ----------------------------- | ------------------- | --------- | ---------------- |
| `dev/*`, `feature/*`, `fix/*` | Active development | - | Contributors |
| `develop` | Integration branch | - | CI validation |
| `alpha` | Early testing | `@alpha` | Internal testers |
| `beta` | Pre-release testing | `@beta` | Beta testers |
| `main` | Production releases | `@latest` | Everyone |
## Workflow Overview
### 1. Feature Development
1. Create a branch: `git checkout -b dev/my-feature`
2. Make changes and push
3. **Auto-PR created** to `develop` via `feature-pr.yml`
4. Get review, iterate, merge to `develop`
### 2. Promotion Through Stages
When code lands in `develop`, the `promote-branch.yml` workflow:
1. Runs tests appropriate for that stage
2. Creates a PR to the next branch (develop → alpha → beta → main)
3. Auto-merges `develop → alpha` if tests pass
4. Requires manual approval for `alpha → beta` and `beta → main`
### 3. Releases
Releases are triggered manually via the **Release** workflow:
1. Go to Actions → Release → Run workflow
2. Select release type: `alpha`, `beta`, or `stable`
3. Workflow runs: version bump → changelog → tests → npm publish → Docker push
## Test Coverage by Stage
| Stage | Tests Run |
| ------- | ----------------------------------------------------- |
| develop | tsgo, lint, format, protocol, unit tests (Node + Bun) |
| alpha | + secrets scan |
| beta | + Windows tests |
| stable | + macOS tests, install smoke tests |
## Emergency Hotfixes
For critical production issues:
1. Create branch: `git checkout -b hotfix/critical-bug`
2. Push → **Auto-PR created** directly to `main`
3. Get expedited review (skip staging)
4. After merge, cherry-pick to `develop`, `alpha`, `beta` to sync
```bash
# After hotfix merges to main
git checkout develop && git cherry-pick <commit-sha> && git push
git checkout alpha && git cherry-pick <commit-sha> && git push
git checkout beta && git cherry-pick <commit-sha> && git push
```
## npm Installation by Channel
```bash
# Stable (default)
npm install -g openclaw
# Beta testing
npm install -g openclaw@beta
# Alpha testing (bleeding edge)
npm install -g openclaw@alpha
```
## Docker Images
Images are published to GitHub Container Registry:
```bash
# Stable
docker pull ghcr.io/openclaw/openclaw:latest
# Beta
docker pull ghcr.io/openclaw/openclaw:beta
# Specific version
docker pull ghcr.io/openclaw/openclaw:2026.2.6
```
## Version Format
- **Stable**: `YYYY.M.D` (e.g., `2026.2.6`)
- **Beta**: `YYYY.M.D-beta.N` (e.g., `2026.2.6-beta.1`)
- **Alpha**: `YYYY.M.D-alpha.N` (e.g., `2026.2.6-alpha.3`)
## Setup
### Required Secrets
Configure these in GitHub repo Settings → Secrets and variables → Actions:
| Secret | Required? | Purpose | How to get it |
| --------------------- | -------------------- | ------------------------------------------------------- | ----------------------------------------------------------- |
| `GITHUB_TOKEN` | Automatic | PR creation, Docker registry, branch ops | Provided by GitHub Actions — no setup needed |
| `NPM_TOKEN` | Yes (for publishing) | npm publish with `@alpha`, `@beta`, `@latest` tags | npmjs.com → Access Tokens → Generate New Token → Automation |
| `DISCORD_WEBHOOK_URL` | Optional | Notifications for promotions, test results, deployments | Discord → Server Settings → Integrations → Webhooks |
Without `NPM_TOKEN`, the pipeline runs normally but skips npm publishing. Without `DISCORD_WEBHOOK_URL`, notifications are silently skipped.
### Branch Setup
Staging branches are auto-created from `main` when the first promotion runs. No manual setup required.
### Rollback
The rollback workflow (`Actions → Rollback`) re-tags npm and Docker to a previous version. Requires `NPM_TOKEN` and is manual-trigger only.

View File

@@ -35,6 +35,7 @@ For a high-level overview, see [Onboarding Wizard](/start/wizard).
- **OpenAI Code (Codex) subscription (OAuth)**: browser flow; paste the `code#state`.
- Sets `agents.defaults.model` to `openai-codex/gpt-5.2` when model is unset or `openai/*`.
- **OpenAI API key**: uses `OPENAI_API_KEY` if present or prompts for a key, then saves it to `~/.openclaw/.env` so launchd can read it.
- **xAI (Grok) API key**: prompts for `XAI_API_KEY` and configures xAI as a model provider.
- **OpenCode Zen (multi-model proxy)**: prompts for `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`, get it at https://opencode.ai/auth).
- **API key**: stores the key for you.
- **Vercel AI Gateway (multi-model proxy)**: prompts for `AI_GATEWAY_API_KEY`.

View File

@@ -15,7 +15,7 @@ In the beginning, there was **Warelay** — a sensible name for a WhatsApp gatew
But then came a space lobster.
For a while, the lobster was called **Clawd**, living in an **OpenClaw**. But in January 2026, Anthropic sent a polite email asking for a name change (trademark stuff). And so the lobster did what lobsters do best:
For a while, the lobster was called **Clawd**, living in a **Clawdbot**. But in January 2026, Anthropic sent a polite email asking for a name change (trademark stuff). And so the lobster did what lobsters do best:
**It molted.**

View File

@@ -80,7 +80,7 @@ When onboarding finishes, we auto-open the dashboard and print a clean (non-toke
OpenClaw reads operating instructions and “memory” from its workspace directory.
By default, OpenClaw uses `~/.openclaw/workspace` as the agent workspace, and will create it (plus starter `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`) automatically on setup/first agent run. `BOOTSTRAP.md` is only created when the workspace is brand new (it should not come back after you delete it).
By default, OpenClaw uses `~/.openclaw/workspace` as the agent workspace, and will create it (plus starter `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`) automatically on setup/first agent run. `BOOTSTRAP.md` is only created when the workspace is brand new (it should not come back after you delete it). `MEMORY.md` is optional (not auto-created); when present, it is loaded for normal sessions. Subagent sessions only inject `AGENTS.md` and `TOOLS.md`.
Tip: treat this folder like OpenClaws “memory” and make it a git repo (ideally private) so your `AGENTS.md` + memory files are backed up. If git is installed, brand-new workspaces are auto-initialized.

View File

@@ -145,6 +145,9 @@ What you set:
Sets `agents.defaults.model` to `openai/gpt-5.1-codex` when model is unset, `openai/*`, or `openai-codex/*`.
</Accordion>
<Accordion title="xAI (Grok) API key">
Prompts for `XAI_API_KEY` and configures xAI as a model provider.
</Accordion>
<Accordion title="OpenCode Zen">
Prompts for `OPENCODE_API_KEY` (or `OPENCODE_ZEN_API_KEY`).
Setup URL: [opencode.ai/auth](https://opencode.ai/auth).

View File

@@ -227,7 +227,7 @@ Narrow, explicit allowlists are fastest and least flaky:
- Google focus (Gemini API key + Antigravity):
- Gemini (API key): `OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Antigravity (OAuth): `OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- Antigravity (OAuth): `OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
Notes:
@@ -250,12 +250,12 @@ This is the “common models” run we expect to keep working:
- OpenAI Codex: `openai-codex/gpt-5.3-codex` (optional: `openai-codex/gpt-5.3-codex-codex`)
- Anthropic: `anthropic/claude-opus-4-6` (or `anthropic/claude-sonnet-4-5`)
- Google (Gemini API): `google/gemini-3-pro-preview` and `google/gemini-3-flash-preview` (avoid older Gemini 2.x models)
- Google (Antigravity): `google-antigravity/claude-opus-4-5-thinking` and `google-antigravity/gemini-3-flash`
- Google (Antigravity): `google-antigravity/claude-opus-4-6-thinking` and `google-antigravity/gemini-3-flash`
- Z.AI (GLM): `zai/glm-4.7`
- MiniMax: `minimax/minimax-m2.1`
Run gateway smoke with tools + image:
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.3-codex,anthropic/claude-opus-4-6,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.3-codex,anthropic/claude-opus-4-6,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
### Baseline: tool calling (Read + optional Exec)

View File

@@ -34,8 +34,7 @@ If you have multiple profiles, pass `--browser-profile <name>` (the default is `
## X/Twitter: recommended flow
- **Read/search/threads:** use the **bird** CLI skill (no browser, stable).
- Repo: [https://github.com/steipete/bird](https://github.com/steipete/bird)
- **Read/search/threads:** use the **host** browser (manual login).
- **Post updates:** use the **host** browser (manual login).
## Sandboxing + host browser access

View File

@@ -102,7 +102,7 @@ Legacy `agents.default` entries are migrated to `agents.main` on load.
Examples:
- `~/Projects/**/bin/bird`
- `~/Projects/**/bin/peekaboo`
- `~/.local/bin/*`
- `/opt/homebrew/bin/rg`

View File

@@ -1,35 +1,36 @@
---
read_when:
- 手动引导工作区
- 手动引导工作区
summary: 智能体身份记录
x-i18n:
generated_at: "2026-02-01T21:37:32Z"
model: claude-opus-4-5
provider: pi
source_hash: 3d60209c36adf7219ec95ecc2031c1f2c8741763d16b73fe7b30835b1d384de0
source_path: reference/templates/IDENTITY.md
workflow: 15
generated_at: "2026-02-01T21:37:32Z"
model: claude-opus-4-5
provider: pi
source_hash: 3d60209c36adf7219ec95ecc2031c1f2c8741763d16b73fe7b30835b1d384de0
source_path: reference/templates/IDENTITY.md
workflow: 15
---
# IDENTITY.md - 我是谁?
*在你的第一次对话中填写此文件。让它属于你。*
_在你的第一次对话中填写此文件。让它属于你。_
- **名称:**
*(选一个你喜欢的)*
_(选一个你喜欢的)_
- **生物类型:**
*AI机器人使魔机器中的幽灵更奇特的东西*
_AI机器人使魔机器中的幽灵更奇特的东西_
- **气质:**
*(你给人什么感觉?犀利?温暖?混乱?沉稳?)*
_(你给人什么感觉?犀利?温暖?混乱?沉稳?)_
- **表情符号:**
*(你的标志 — 选一个感觉对的)*
_(你的标志 — 选一个感觉对的)_
- **头像:**
*工作区相对路径、http(s) URL 或 data URI*
_工作区相对路径、http(s) URL 或 data URI_
---
这不仅仅是元数据。这是探索你是谁的开始。
注意事项:
- 将此文件保存在工作区根目录,命名为 `IDENTITY.md`
- 头像请使用工作区相对路径,例如 `avatars/openclaw.png`

View File

@@ -1,29 +1,29 @@
---
read_when:
- 手动引导工作区
- 手动引导工作区
summary: 用户档案记录
x-i18n:
generated_at: "2026-02-01T21:38:04Z"
model: claude-opus-4-5
provider: pi
source_hash: 508dfcd4648512df712eaf8ca5d397a925d8035bac5bf2357e44d6f52f9fa9a6
source_path: reference/templates/USER.md
workflow: 15
generated_at: "2026-02-01T21:38:04Z"
model: claude-opus-4-5
provider: pi
source_hash: 508dfcd4648512df712eaf8ca5d397a925d8035bac5bf2357e44d6f52f9fa9a6
source_path: reference/templates/USER.md
workflow: 15
---
# USER.md - 关于你的用户
*了解你正在帮助的人。随时更新此文件。*
_了解你正在帮助的人。随时更新此文件。_
- **姓名:**
- **称呼方式:**
- **代词:** *(可选)*
- **代词:** _(可选)_
- **时区:**
- **备注:**
## 背景
*(他们关心什么?正在做什么项目?什么让他们烦恼?什么让他们开心?随着时间推移逐步完善。)*
_(他们关心什么?正在做什么项目?什么让他们烦恼?什么让他们开心?随着时间推移逐步完善。)_
---

View File

@@ -234,7 +234,7 @@ OPENCLAW_LIVE_CLI_BACKEND=1 \
- Google 专项Gemini API 密钥 + Antigravity
- GeminiAPI 密钥):`OPENCLAW_LIVE_GATEWAY_MODELS="google/gemini-3-flash-preview" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- AntigravityOAuth`OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
- AntigravityOAuth`OPENCLAW_LIVE_GATEWAY_MODELS="google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-pro-high" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
注意:
@@ -257,12 +257,12 @@ OPENCLAW_LIVE_CLI_BACKEND=1 \
- OpenAI Codex`openai-codex/gpt-5.2`(可选:`openai-codex/gpt-5.2-codex`
- Anthropic`anthropic/claude-opus-4-5`(或 `anthropic/claude-sonnet-4-5`
- GoogleGemini API`google/gemini-3-pro-preview``google/gemini-3-flash-preview`(避免较旧的 Gemini 2.x 模型)
- GoogleAntigravity`google-antigravity/claude-opus-4-5-thinking``google-antigravity/gemini-3-flash`
- GoogleAntigravity`google-antigravity/claude-opus-4-6-thinking``google-antigravity/gemini-3-flash`
- Z.AIGLM`zai/glm-4.7`
- MiniMax`minimax/minimax-m2.1`
运行带工具 + 图像的 Gateway 网关冒烟测试:
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.2,anthropic/claude-opus-4-5,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-5-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
`OPENCLAW_LIVE_GATEWAY_MODELS="openai/gpt-5.2,openai-codex/gpt-5.2,anthropic/claude-opus-4-5,google/gemini-3-pro-preview,google/gemini-3-flash-preview,google-antigravity/claude-opus-4-6-thinking,google-antigravity/gemini-3-flash,zai/glm-4.7,minimax/minimax-m2.1" pnpm test:live src/gateway/gateway-models.profiles.live.test.ts`
### 基线工具调用Read + 可选 Exec

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/bluebubbles",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw BlueBubbles channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -26,7 +26,9 @@ const AUDIO_MIME_CAF = new Set(["audio/x-caf", "audio/caf"]);
function sanitizeFilename(input: string | undefined, fallback: string): string {
const trimmed = input?.trim() ?? "";
const base = trimmed ? path.basename(trimmed) : "";
return base || fallback;
const name = base || fallback;
// Strip characters that could enable multipart header injection (CWE-93)
return name.replace(/[\r\n"\\]/g, "_");
}
function ensureExtension(filename: string, extension: string, fallbackBase: string): string {

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk";
import crypto from "node:crypto";
import path from "node:path";
import { resolveBlueBubblesAccount } from "./accounts.js";
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
@@ -336,10 +337,13 @@ export async function setGroupIconBlueBubbles(
const parts: Uint8Array[] = [];
const encoder = new TextEncoder();
// Sanitize filename to prevent multipart header injection (CWE-93)
const safeFilename = path.basename(filename).replace(/[\r\n"\\]/g, "_") || "icon.png";
// Add file field named "icon" as per API spec
parts.push(encoder.encode(`--${boundary}\r\n`));
parts.push(
encoder.encode(`Content-Disposition: form-data; name="icon"; filename="${filename}"\r\n`),
encoder.encode(`Content-Disposition: form-data; name="icon"; filename="${safeFilename}"\r\n`),
);
parts.push(
encoder.encode(`Content-Type: ${opts.contentType ?? "application/octet-stream"}\r\n\r\n`),

View File

@@ -393,6 +393,48 @@ describe("BlueBubbles webhook monitor", () => {
expect(res.statusCode).toBe(400);
});
it("returns 400 when request body times out (Slow-Loris protection)", async () => {
vi.useFakeTimers();
try {
const account = createMockAccount();
const config: OpenClawConfig = {};
const core = createMockRuntime();
setBlueBubblesRuntime(core);
unregister = registerBlueBubblesWebhookTarget({
account,
config,
runtime: { log: vi.fn(), error: vi.fn() },
core,
path: "/bluebubbles-webhook",
});
// Create a request that never sends data or ends (simulates slow-loris)
const req = new EventEmitter() as IncomingMessage;
req.method = "POST";
req.url = "/bluebubbles-webhook";
req.headers = {};
(req as unknown as { socket: { remoteAddress: string } }).socket = {
remoteAddress: "127.0.0.1",
};
req.destroy = vi.fn();
const res = createMockResponse();
const handledPromise = handleBlueBubblesWebhookRequest(req, res);
// Advance past the 30s timeout
await vi.advanceTimersByTimeAsync(31_000);
const handled = await handledPromise;
expect(handled).toBe(true);
expect(res.statusCode).toBe(400);
expect(req.destroy).toHaveBeenCalled();
} finally {
vi.useRealTimers();
}
});
it("authenticates via password query parameter", async () => {
const account = createMockAccount({ password: "secret-token" });
const config: OpenClawConfig = {};

View File

@@ -508,14 +508,29 @@ export function registerBlueBubblesWebhookTarget(target: WebhookTarget): () => v
};
}
async function readJsonBody(req: IncomingMessage, maxBytes: number) {
async function readJsonBody(req: IncomingMessage, maxBytes: number, timeoutMs = 30_000) {
const chunks: Buffer[] = [];
let total = 0;
return await new Promise<{ ok: boolean; value?: unknown; error?: string }>((resolve) => {
let done = false;
const finish = (result: { ok: boolean; value?: unknown; error?: string }) => {
if (done) {
return;
}
done = true;
clearTimeout(timer);
resolve(result);
};
const timer = setTimeout(() => {
finish({ ok: false, error: "request body timeout" });
req.destroy();
}, timeoutMs);
req.on("data", (chunk: Buffer) => {
total += chunk.length;
if (total > maxBytes) {
resolve({ ok: false, error: "payload too large" });
finish({ ok: false, error: "payload too large" });
req.destroy();
return;
}
@@ -525,27 +540,30 @@ async function readJsonBody(req: IncomingMessage, maxBytes: number) {
try {
const raw = Buffer.concat(chunks).toString("utf8");
if (!raw.trim()) {
resolve({ ok: false, error: "empty payload" });
finish({ ok: false, error: "empty payload" });
return;
}
try {
resolve({ ok: true, value: JSON.parse(raw) as unknown });
finish({ ok: true, value: JSON.parse(raw) as unknown });
return;
} catch {
const params = new URLSearchParams(raw);
const payload = params.get("payload") ?? params.get("data") ?? params.get("message");
if (payload) {
resolve({ ok: true, value: JSON.parse(payload) as unknown });
finish({ ok: true, value: JSON.parse(payload) as unknown });
return;
}
throw new Error("invalid json");
}
} catch (err) {
resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
finish({ ok: false, error: err instanceof Error ? err.message : String(err) });
}
});
req.on("error", (err) => {
resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
finish({ ok: false, error: err instanceof Error ? err.message : String(err) });
});
req.on("close", () => {
finish({ ok: false, error: "connection closed" });
});
});
}

View File

@@ -16,7 +16,9 @@ export type BlueBubblesServerInfo = {
computer_id?: string;
};
/** Cache server info by account ID to avoid repeated API calls */
/** Cache server info by account ID to avoid repeated API calls.
* Size-capped to prevent unbounded growth (#4948). */
const MAX_SERVER_INFO_CACHE_SIZE = 64;
const serverInfoCache = new Map<string, { info: BlueBubblesServerInfo; expires: number }>();
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
@@ -56,6 +58,13 @@ export async function fetchBlueBubblesServerInfo(params: {
const data = payload?.data as BlueBubblesServerInfo | undefined;
if (data) {
serverInfoCache.set(cacheKey, { info: data, expires: Date.now() + CACHE_TTL_MS });
// Evict oldest entries if cache exceeds max size
if (serverInfoCache.size > MAX_SERVER_INFO_CACHE_SIZE) {
const oldest = serverInfoCache.keys().next().value;
if (oldest !== undefined) {
serverInfoCache.delete(oldest);
}
}
}
return data ?? null;
} catch {

View File

@@ -370,6 +370,16 @@ describe("send", () => {
).rejects.toThrow("requires text");
});
it("throws when text becomes empty after markdown stripping", async () => {
// Edge case: input like "***" or "---" passes initial check but becomes empty after stripMarkdown
await expect(
sendMessageBlueBubbles("+15551234567", "***", {
serverUrl: "http://localhost:1234",
password: "test",
}),
).rejects.toThrow("empty after markdown removal");
});
it("throws when serverUrl is missing", async () => {
await expect(sendMessageBlueBubbles("+15551234567", "Hello", {})).rejects.toThrow(
"serverUrl is required",
@@ -438,6 +448,77 @@ describe("send", () => {
expect(body.method).toBeUndefined();
});
it("strips markdown formatting from outbound messages", async () => {
mockFetch
.mockResolvedValueOnce({
ok: true,
json: () =>
Promise.resolve({
data: [
{
guid: "iMessage;-;+15551234567",
participants: [{ address: "+15551234567" }],
},
],
}),
})
.mockResolvedValueOnce({
ok: true,
text: () =>
Promise.resolve(
JSON.stringify({
data: { guid: "msg-uuid-stripped" },
}),
),
});
const result = await sendMessageBlueBubbles(
"+15551234567",
"**Bold** and *italic* with `code`\n## Header",
{
serverUrl: "http://localhost:1234",
password: "test",
},
);
expect(result.messageId).toBe("msg-uuid-stripped");
const sendCall = mockFetch.mock.calls[1];
const body = JSON.parse(sendCall[1].body);
// Markdown should be stripped: no asterisks, backticks, or hashes
expect(body.message).toBe("Bold and italic with code\nHeader");
});
it("strips markdown when creating a new chat", async () => {
mockFetch
.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ data: [] }),
})
.mockResolvedValueOnce({
ok: true,
text: () =>
Promise.resolve(
JSON.stringify({
data: { guid: "new-msg-stripped" },
}),
),
});
const result = await sendMessageBlueBubbles("+15550009999", "**Welcome** to the _chat_!", {
serverUrl: "http://localhost:1234",
password: "test",
});
expect(result.messageId).toBe("new-msg-stripped");
const createCall = mockFetch.mock.calls[1];
expect(createCall[0]).toContain("/api/v1/chat/new");
const body = JSON.parse(createCall[1].body);
// Markdown should be stripped
expect(body.message).toBe("Welcome to the chat!");
});
it("creates a new chat when handle target is missing", async () => {
mockFetch
.mockResolvedValueOnce({

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk";
import crypto from "node:crypto";
import { stripMarkdown } from "openclaw/plugin-sdk";
import { resolveBlueBubblesAccount } from "./accounts.js";
import {
extractHandleFromChatGuid,
@@ -332,6 +333,7 @@ async function createNewChatWithMessage(params: {
const payload = {
addresses: [params.address],
message: params.message,
tempGuid: `temp-${crypto.randomUUID()}`,
};
const res = await blueBubblesFetchWithTimeout(
url,
@@ -377,6 +379,11 @@ export async function sendMessageBlueBubbles(
if (!trimmedText.trim()) {
throw new Error("BlueBubbles send requires text");
}
// Strip markdown early and validate - ensures messages like "***" or "---" don't become empty
const strippedText = stripMarkdown(trimmedText);
if (!strippedText.trim()) {
throw new Error("BlueBubbles send requires text (message was empty after markdown removal)");
}
const account = resolveBlueBubblesAccount({
cfg: opts.cfg ?? {},
@@ -406,7 +413,7 @@ export async function sendMessageBlueBubbles(
baseUrl,
password,
address: target.address,
message: trimmedText,
message: strippedText,
timeoutMs: opts.timeoutMs,
});
}
@@ -419,7 +426,7 @@ export async function sendMessageBlueBubbles(
const payload: Record<string, unknown> = {
chatGuid,
tempGuid: crypto.randomUUID(),
message: trimmedText,
message: strippedText,
};
if (needsPrivateApi) {
payload.method = "private-api";

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/copilot-proxy",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Copilot Proxy provider plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/diagnostics-otel",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw diagnostics OpenTelemetry exporter",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/discord",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Discord channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,11 +1,11 @@
{
"name": "@openclaw/feishu",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
"type": "module",
"dependencies": {
"@larksuiteoapi/node-sdk": "^1.56.1",
"@sinclair/typebox": "^0.34.48",
"@larksuiteoapi/node-sdk": "^1.58.0",
"@sinclair/typebox": "0.34.48",
"zod": "^4.3.6"
},
"devDependencies": {

View File

@@ -13,7 +13,7 @@ const REDIRECT_URI = "http://localhost:51121/oauth-callback";
const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
const TOKEN_URL = "https://oauth2.googleapis.com/token";
const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
const DEFAULT_MODEL = "google-antigravity/claude-opus-4-5-thinking";
const DEFAULT_MODEL = "google-antigravity/claude-opus-4-6-thinking";
const SCOPES = [
"https://www.googleapis.com/auth/cloud-platform",

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/google-antigravity-auth",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Google Antigravity OAuth provider plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/google-gemini-cli-auth",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Gemini CLI OAuth provider plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/googlechat",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Google Chat channel plugin",
"type": "module",
"dependencies": {

View File

@@ -8,6 +8,8 @@ const ADDON_ISSUER_PATTERN = /^service-\d+@gcp-sa-gsuiteaddons\.iam\.gserviceacc
const CHAT_CERTS_URL =
"https://www.googleapis.com/service_accounts/v1/metadata/x509/chat@system.gserviceaccount.com";
// Size-capped to prevent unbounded growth in long-running deployments (#4948)
const MAX_AUTH_CACHE_SIZE = 32;
const authCache = new Map<string, { key: string; auth: GoogleAuth }>();
const verifyClient = new OAuth2Client();
@@ -30,20 +32,32 @@ function getAuthInstance(account: ResolvedGoogleChatAccount): GoogleAuth {
return cached.auth;
}
const evictOldest = () => {
if (authCache.size > MAX_AUTH_CACHE_SIZE) {
const oldest = authCache.keys().next().value;
if (oldest !== undefined) {
authCache.delete(oldest);
}
}
};
if (account.credentialsFile) {
const auth = new GoogleAuth({ keyFile: account.credentialsFile, scopes: [CHAT_SCOPE] });
authCache.set(account.accountId, { key, auth });
evictOldest();
return auth;
}
if (account.credentials) {
const auth = new GoogleAuth({ credentials: account.credentials, scopes: [CHAT_SCOPE] });
authCache.set(account.accountId, { key, auth });
evictOldest();
return auth;
}
const auth = new GoogleAuth({ scopes: [CHAT_SCOPE] });
authCache.set(account.accountId, { key, auth });
evictOldest();
return auth;
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/imessage",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw iMessage channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/line",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw LINE channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/llm-task",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw JSON-only LLM task plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/lobster",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
"type": "module",
"devDependencies": {

View File

@@ -1,5 +1,23 @@
# Changelog
## 2026.2.6-3
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.6-2
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.6
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.4
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/matrix",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Matrix channel plugin",
"type": "module",
"dependencies": {

View File

@@ -17,7 +17,18 @@ export function normalizeThreadId(raw?: string | number | null): string | null {
return trimmed ? trimmed : null;
}
// Size-capped to prevent unbounded growth (#4948)
const MAX_DIRECT_ROOM_CACHE_SIZE = 1024;
const directRoomCache = new Map<string, string>();
function setDirectRoomCached(key: string, value: string): void {
directRoomCache.set(key, value);
if (directRoomCache.size > MAX_DIRECT_ROOM_CACHE_SIZE) {
const oldest = directRoomCache.keys().next().value;
if (oldest !== undefined) {
directRoomCache.delete(oldest);
}
}
}
async function persistDirectRoom(
client: MatrixClient,
@@ -62,7 +73,7 @@ async function resolveDirectRoomId(client: MatrixClient, userId: string): Promis
const directContent = await client.getAccountData(EventType.Direct);
const list = Array.isArray(directContent?.[trimmed]) ? directContent[trimmed] : [];
if (list.length > 0) {
directRoomCache.set(trimmed, list[0]);
setDirectRoomCached(trimmed, list[0]);
return list[0];
}
} catch {
@@ -86,7 +97,7 @@ async function resolveDirectRoomId(client: MatrixClient, userId: string): Promis
}
// Prefer classic 1:1 rooms, but allow larger rooms if requested.
if (members.length === 2) {
directRoomCache.set(trimmed, roomId);
setDirectRoomCached(trimmed, roomId);
await persistDirectRoom(client, trimmed, roomId);
return roomId;
}
@@ -99,7 +110,7 @@ async function resolveDirectRoomId(client: MatrixClient, userId: string): Promis
}
if (fallbackRoom) {
directRoomCache.set(trimmed, fallbackRoom);
setDirectRoomCached(trimmed, fallbackRoom);
await persistDirectRoom(client, trimmed, fallbackRoom);
return fallbackRoom;
}

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/mattermost",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Mattermost channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/memory-core",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw core memory search plugin",
"type": "module",
"devDependencies": {

View File

@@ -6,8 +6,8 @@
* Provides seamless auto-recall and auto-capture via lifecycle hooks.
*/
import type * as LanceDB from "@lancedb/lancedb";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import * as lancedb from "@lancedb/lancedb";
import { Type } from "@sinclair/typebox";
import { randomUUID } from "node:crypto";
import OpenAI from "openai";
@@ -23,6 +23,19 @@ import {
// Types
// ============================================================================
let lancedbImportPromise: Promise<typeof import("@lancedb/lancedb")> | null = null;
const loadLanceDB = async (): Promise<typeof import("@lancedb/lancedb")> => {
if (!lancedbImportPromise) {
lancedbImportPromise = import("@lancedb/lancedb");
}
try {
return await lancedbImportPromise;
} catch (err) {
// Common on macOS today: upstream package may not ship darwin native bindings.
throw new Error(`memory-lancedb: failed to load LanceDB. ${String(err)}`);
}
};
type MemoryEntry = {
id: string;
text: string;
@@ -44,8 +57,8 @@ type MemorySearchResult = {
const TABLE_NAME = "memories";
class MemoryDB {
private db: lancedb.Connection | null = null;
private table: lancedb.Table | null = null;
private db: LanceDB.Connection | null = null;
private table: LanceDB.Table | null = null;
private initPromise: Promise<void> | null = null;
constructor(
@@ -66,6 +79,7 @@ class MemoryDB {
}
private async doInitialize(): Promise<void> {
const lancedb = await loadLanceDB();
this.db = await lancedb.connect(this.dbPath);
const tables = await this.db.tableNames();

View File

@@ -1,10 +1,10 @@
{
"name": "@openclaw/memory-lancedb",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture",
"type": "module",
"dependencies": {
"@lancedb/lancedb": "^0.23.0",
"@lancedb/lancedb": "^0.24.1",
"@sinclair/typebox": "0.34.48",
"openai": "^6.18.0"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/minimax-portal-auth",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw MiniMax Portal OAuth provider plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,5 +1,23 @@
# Changelog
## 2026.2.6-3
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.6-2
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.6
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.4
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/msteams",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Microsoft Teams channel plugin",
"type": "module",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/nextcloud-talk",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Nextcloud Talk channel plugin",
"type": "module",
"devDependencies": {

View File

@@ -1,5 +1,23 @@
# Changelog
## 2026.2.6-3
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.6-2
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.6
### Changes
- Version alignment with core OpenClaw release numbers.
## 2026.2.4
### Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openclaw/nostr",
"version": "2026.2.4",
"version": "2026.2.6-3",
"description": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs",
"type": "module",
"dependencies": {

Some files were not shown because too many files have changed in this diff Show More