Compare commits
27 Commits
dev/ci
...
fix/state-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ff9e4830c | ||
|
|
6ffa76ed81 | ||
|
|
c923fb0d5b | ||
|
|
05a57e94a4 | ||
|
|
c27b03794a | ||
|
|
9866a857a7 | ||
|
|
e2dea2684f | ||
|
|
a30c4f45c3 | ||
|
|
95263f4e60 | ||
|
|
6f1ba986b3 | ||
|
|
c741d008dd | ||
|
|
0d60ef6fef | ||
|
|
ce715c4c56 | ||
|
|
0deb8b0da1 | ||
|
|
b8c8130efe | ||
|
|
ea423bbbfd | ||
|
|
980f788731 | ||
|
|
9271fcb3d4 | ||
|
|
b8f740fb14 | ||
|
|
8da20027c4 | ||
|
|
9201e140cb | ||
|
|
ff80646085 | ||
|
|
929a3725d3 | ||
|
|
cde29fef71 | ||
|
|
6d1daf2ba5 | ||
|
|
82419eaad6 | ||
|
|
f0722498a4 |
126
.agents/skills/PR_WORKFLOW.md
Normal file
126
.agents/skills/PR_WORKFLOW.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# PR Review Instructions
|
||||
|
||||
Please read this in full and do not skip sections.
|
||||
|
||||
## Working rule
|
||||
|
||||
Skills execute workflow, maintainers provide judgment.
|
||||
Always pause between skills to evaluate technical direction, not just command success.
|
||||
|
||||
These three skills must be used in order:
|
||||
|
||||
1. `review-pr`
|
||||
2. `prepare-pr`
|
||||
3. `merge-pr`
|
||||
|
||||
They are necessary, but not sufficient. Maintainers must steer between steps and understand the code before moving forward.
|
||||
|
||||
Treat PRs as reports first, code second.
|
||||
If submitted code is low quality, ignore it and implement the best solution for the problem.
|
||||
|
||||
Do not continue if you cannot verify the problem is real or test the fix.
|
||||
|
||||
## PR quality bar
|
||||
|
||||
- Do not trust PR code by default.
|
||||
- Do not merge changes you cannot validate with a reproducible problem and a tested fix.
|
||||
- Keep types strict. Do not use `any` in implementation code.
|
||||
- Keep external-input boundaries typed and validated, including CLI input, environment variables, network payloads, and tool output.
|
||||
- Keep implementations properly scoped. Fix root causes, not local symptoms.
|
||||
- Identify and reuse canonical sources of truth so behavior does not drift across the codebase.
|
||||
- Harden changes. Always evaluate security impact and abuse paths.
|
||||
- Understand the system before changing it. Never make the codebase messier just to clear a PR queue.
|
||||
|
||||
## Unified workflow
|
||||
|
||||
Entry criteria:
|
||||
|
||||
- PR URL/number is known.
|
||||
- Problem statement is clear enough to attempt reproduction.
|
||||
- A realistic verification path exists (tests, integration checks, or explicit manual validation).
|
||||
|
||||
### 1) `review-pr`
|
||||
|
||||
Purpose:
|
||||
|
||||
- Review only: correctness, value, security risk, tests, docs, and changelog impact.
|
||||
- Produce structured findings and a recommendation.
|
||||
|
||||
Expected output:
|
||||
|
||||
- Recommendation: ready, needs work, needs discussion, or close.
|
||||
- `.local/review.md` with actionable findings.
|
||||
|
||||
Maintainer checkpoint before `prepare-pr`:
|
||||
|
||||
```
|
||||
What problem are they trying to solve?
|
||||
What is the most optimal implementation?
|
||||
Is the code properly scoped?
|
||||
Can we fix up everything?
|
||||
Do we have any questions?
|
||||
```
|
||||
|
||||
Stop and escalate instead of continuing if:
|
||||
|
||||
- The problem cannot be reproduced or confirmed.
|
||||
- The proposed PR scope does not match the stated problem.
|
||||
- The design introduces unresolved security or trust-boundary concerns.
|
||||
|
||||
### 2) `prepare-pr`
|
||||
|
||||
Purpose:
|
||||
|
||||
- Make the PR merge-ready on its head branch.
|
||||
- Rebase onto current `main`, fix blocker/important findings, and run gates.
|
||||
|
||||
Expected output:
|
||||
|
||||
- Updated code and tests on the PR head branch.
|
||||
- `.local/prep.md` with changes, verification, and current HEAD SHA.
|
||||
- Final status: `PR is ready for /mergepr`.
|
||||
|
||||
Maintainer checkpoint before `merge-pr`:
|
||||
|
||||
```
|
||||
Is this the most optimal implementation?
|
||||
Is the code properly scoped?
|
||||
Is the code properly typed?
|
||||
Is the code hardened?
|
||||
Do we have enough tests?
|
||||
Are tests using fake timers where relevant? (e.g., debounce/throttle, retry backoff, timeout branches, delayed callbacks, polling loops)
|
||||
Do not add performative tests, ensure tests are real and there are no regressions.
|
||||
Take your time, fix it properly, refactor if necessary.
|
||||
Do you see any follow-up refactors we should do?
|
||||
Did any changes introduce any potential security vulnerabilities?
|
||||
```
|
||||
|
||||
Stop and escalate instead of continuing if:
|
||||
|
||||
- You cannot verify behavior changes with meaningful tests or validation.
|
||||
- Fixing findings requires broad architecture changes outside safe PR scope.
|
||||
- Security hardening requirements remain unresolved.
|
||||
|
||||
### 3) `merge-pr`
|
||||
|
||||
Purpose:
|
||||
|
||||
- Merge only after review and prep artifacts are present and checks are green.
|
||||
- Use squash merge flow and verify the PR ends in `MERGED` state.
|
||||
|
||||
Go or no-go checklist before merge:
|
||||
|
||||
- All BLOCKER and IMPORTANT findings are resolved.
|
||||
- Verification is meaningful and regression risk is acceptably low.
|
||||
- Docs and changelog are updated when required.
|
||||
- Required CI checks are green and the branch is not behind `main`.
|
||||
|
||||
Expected output:
|
||||
|
||||
- Successful merge commit and recorded merge SHA.
|
||||
- Worktree cleanup after successful merge.
|
||||
|
||||
Maintainer checkpoint after merge:
|
||||
|
||||
- Were any refactors intentionally deferred and now need follow-up issue(s)?
|
||||
- Did this reveal broader architecture or test gaps we should address?
|
||||
@@ -28,7 +28,7 @@ Merge a prepared PR via `gh pr merge --squash` and clean up the worktree after s
|
||||
|
||||
## Known Footguns
|
||||
|
||||
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/Development/openclaw`, not `~/openclaw`.
|
||||
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/dev/openclaw` if available; otherwise ask user.
|
||||
- Read `.local/review.md` and `.local/prep.md` in the worktree. Do not skip.
|
||||
- Clean up the real worktree directory `.worktrees/pr-<PR>` only after a successful merge.
|
||||
- Expect cleanup to remove `.local/` artifacts.
|
||||
@@ -49,7 +49,7 @@ Create a checklist of all merge steps, print it, then continue and execute the c
|
||||
Use an isolated worktree for all merge work.
|
||||
|
||||
```sh
|
||||
cd ~/Development/openclaw
|
||||
cd ~/dev/openclaw
|
||||
# Sanity: confirm you are in the repo
|
||||
git rev-parse --show-toplevel
|
||||
|
||||
@@ -167,7 +167,7 @@ gh pr view <PR> --json state --jq .state
|
||||
Run cleanup only if step 6 returned `MERGED`.
|
||||
|
||||
```sh
|
||||
cd ~/Development/openclaw
|
||||
cd ~/dev/openclaw
|
||||
|
||||
git worktree remove ".worktrees/pr-<PR>" --force
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Prepare a PR branch for merge with review fixes, green gates, and an updated hea
|
||||
|
||||
## Known Footguns
|
||||
|
||||
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/openclaw`.
|
||||
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/dev/openclaw` if available; otherwise ask user.
|
||||
- Do not run `git clean -fdx`.
|
||||
- Do not run `git add -A` or `git add .`.
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Perform a thorough review-only PR assessment and return a structured recommendat
|
||||
|
||||
## Known Failure Modes
|
||||
|
||||
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/openclaw`.
|
||||
- If you see "fatal: not a git repository", you are in the wrong directory. Use `~/dev/openclaw` if available; otherwise ask user.
|
||||
- Do not stop after printing the checklist. That is not completion.
|
||||
|
||||
## Writing Style for Output
|
||||
@@ -51,7 +51,7 @@ Create a checklist of all review steps, print it, then continue and execute the
|
||||
Use an isolated worktree for all review work.
|
||||
|
||||
```sh
|
||||
cd ~/Development/openclaw
|
||||
cd ~/dev/openclaw
|
||||
# Sanity: confirm you are in the repo
|
||||
git rev-parse --show-toplevel
|
||||
|
||||
|
||||
41
.github/actions/detect-docs-only/action.yml
vendored
Normal file
41
.github/actions/detect-docs-only/action.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Detect docs-only changes
|
||||
description: >
|
||||
Outputs docs_only=true when all changed files are under docs/ or are
|
||||
markdown (.md/.mdx). Fail-safe: if detection fails, outputs false (run
|
||||
everything). Uses git diff — no API calls, no extra permissions needed.
|
||||
|
||||
outputs:
|
||||
docs_only:
|
||||
description: "'true' if all changes are docs/markdown, 'false' otherwise"
|
||||
value: ${{ steps.check.outputs.docs_only }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Detect docs-only changes
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
BASE="${{ github.event.before }}"
|
||||
else
|
||||
# Use the exact base SHA from the event payload — stable regardless
|
||||
# of base branch movement (avoids origin/<ref> drift).
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
fi
|
||||
|
||||
# Fail-safe: if we can't diff, assume non-docs (run everything)
|
||||
CHANGED=$(git diff --name-only "$BASE" HEAD 2>/dev/null || echo "UNKNOWN")
|
||||
if [ "$CHANGED" = "UNKNOWN" ] || [ -z "$CHANGED" ]; then
|
||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if all changed files are docs or markdown
|
||||
NON_DOCS=$(echo "$CHANGED" | grep -vE '^docs/|\.md$|\.mdx$' || true)
|
||||
if [ -z "$NON_DOCS" ]; then
|
||||
echo "docs_only=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Docs-only change detected — skipping heavy jobs"
|
||||
else
|
||||
echo "docs_only=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
69
.github/actions/discord-notify/action.yml
vendored
69
.github/actions/discord-notify/action.yml
vendored
@@ -1,69 +0,0 @@
|
||||
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 }}"
|
||||
135
.github/workflows/ci.yml
vendored
135
.github/workflows/ci.yml
vendored
@@ -1,22 +1,35 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_call:
|
||||
# Called by testing-strategy.yml for staged releases
|
||||
inputs:
|
||||
test_stage:
|
||||
description: "Testing stage: develop, alpha, beta, or stable. Controls which platform tests run."
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
group: ci-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Detect docs-only changes to skip heavy jobs (test, build, Windows, macOS, Android).
|
||||
# Lint and format always run. Fail-safe: if detection fails, run everything.
|
||||
docs-scope:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docs_only: ${{ steps.check.outputs.docs_only }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: false
|
||||
|
||||
- name: Detect docs-only changes
|
||||
id: check
|
||||
uses: ./.github/actions/detect-docs-only
|
||||
|
||||
install-check:
|
||||
needs: [docs-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -77,6 +90,8 @@ jobs:
|
||||
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
||||
|
||||
checks:
|
||||
needs: [docs-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -85,18 +100,12 @@ jobs:
|
||||
- runtime: node
|
||||
task: tsgo
|
||||
command: pnpm tsgo
|
||||
- runtime: node
|
||||
task: lint
|
||||
command: pnpm build && pnpm lint
|
||||
- runtime: node
|
||||
task: test
|
||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||
- runtime: node
|
||||
task: protocol
|
||||
command: pnpm protocol:check
|
||||
- runtime: node
|
||||
task: format
|
||||
command: pnpm format
|
||||
- runtime: bun
|
||||
task: test
|
||||
command: pnpm canvas:a2ui:bundle && bunx vitest run
|
||||
@@ -167,6 +176,84 @@ jobs:
|
||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||
run: ${{ matrix.command }}
|
||||
|
||||
# Lint and format always run, even on docs-only changes.
|
||||
checks-lint:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- task: lint
|
||||
command: pnpm build && pnpm lint
|
||||
- task: format
|
||||
command: pnpm format
|
||||
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
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.x
|
||||
check-latest: true
|
||||
|
||||
- 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: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Runtime versions
|
||||
run: |
|
||||
node -v
|
||||
npm -v
|
||||
bun -v
|
||||
pnpm -v
|
||||
|
||||
- name: Capture node path
|
||||
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
CI: true
|
||||
run: |
|
||||
export PATH="$NODE_BIN:$PATH"
|
||||
which node
|
||||
node -v
|
||||
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: ${{ matrix.command }}
|
||||
|
||||
secrets:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
@@ -193,10 +280,8 @@ jobs:
|
||||
fi
|
||||
|
||||
checks-windows:
|
||||
# Windows tests: beta+ staging only (not on regular PRs to save compute)
|
||||
if: |
|
||||
inputs.test_stage == 'beta' ||
|
||||
inputs.test_stage == 'stable'
|
||||
needs: [docs-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||
runs-on: blacksmith-4vcpu-windows-2025
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
@@ -305,9 +390,13 @@ jobs:
|
||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||
run: ${{ matrix.command }}
|
||||
|
||||
checks-macos:
|
||||
# macOS tests: stable staging only (not on regular PRs to save compute)
|
||||
if: inputs.test_stage == 'stable'
|
||||
# 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:
|
||||
needs: [docs-scope]
|
||||
if: github.event_name == 'pull_request' && needs.docs-scope.outputs.docs_only != 'true'
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -592,6 +681,8 @@ jobs:
|
||||
PY
|
||||
|
||||
android:
|
||||
needs: [docs-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
349
.github/workflows/deployment-strategy.yml
vendored
349
.github/workflows/deployment-strategy.yml
vendored
@@ -1,349 +0,0 @@
|
||||
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"
|
||||
94
.github/workflows/feature-pr.yml
vendored
94
.github/workflows/feature-pr.yml
vendored
@@ -1,94 +0,0 @@
|
||||
name: Feature PR
|
||||
|
||||
# Auto-create PR from dev/* branches to develop
|
||||
# This is the entry point for new features into the staging pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "dev/**"
|
||||
- "feature/**"
|
||||
- "fix/**"
|
||||
|
||||
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"
|
||||
129
.github/workflows/generate-changelog.yml
vendored
129
.github/workflows/generate-changelog.yml
vendored
@@ -1,129 +0,0 @@
|
||||
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
|
||||
93
.github/workflows/hotfix-pr.yml
vendored
93
.github/workflows/hotfix-pr.yml
vendored
@@ -1,93 +0,0 @@
|
||||
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)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "hotfix/**"
|
||||
|
||||
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"
|
||||
16
.github/workflows/install-smoke.yml
vendored
16
.github/workflows/install-smoke.yml
vendored
@@ -11,7 +11,23 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docs-scope:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docs_only: ${{ steps.check.outputs.docs_only }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect docs-only changes
|
||||
id: check
|
||||
uses: ./.github/actions/detect-docs-only
|
||||
|
||||
install-smoke:
|
||||
needs: [docs-scope]
|
||||
if: needs.docs-scope.outputs.docs_only != 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout CLI
|
||||
|
||||
319
.github/workflows/promote-branch.yml
vendored
319
.github/workflows/promote-branch.yml
vendored
@@ -1,319 +0,0 @@
|
||||
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)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- alpha
|
||||
- beta
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "*.md"
|
||||
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"
|
||||
264
.github/workflows/release-orchestrator.yml
vendored
264
.github/workflows/release-orchestrator.yml
vendored
@@ -1,264 +0,0 @@
|
||||
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
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "*.md"
|
||||
- ".github/workflows/docs-*.yml"
|
||||
|
||||
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
51
.github/workflows/release.yml
vendored
@@ -1,51 +0,0 @@
|
||||
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
256
.github/workflows/rollback.yml
vendored
@@ -1,256 +0,0 @@
|
||||
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
153
.github/workflows/testing-strategy.yml
vendored
@@ -1,153 +0,0 @@
|
||||
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
188
.github/workflows/version-operations.yml
vendored
@@ -1,188 +0,0 @@
|
||||
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"
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -2,6 +2,22 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## 2026.2.6-4
|
||||
|
||||
### Added
|
||||
|
||||
- Gateway: add `agents.create`, `agents.update`, `agents.delete` RPC methods for web UI agent management. (#11045) Thanks @advaitpaliwal.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents: recover from context overflow caused by oversized tool results (pre-emptive capping + fallback truncation). (#11579) Thanks @tyler6204.
|
||||
- Gateway/CLI: when `gateway.bind=lan`, use a LAN IP for probe URLs and Control UI links. (#11448) Thanks @AnonO6.
|
||||
- Memory: set Voyage embeddings `input_type` for improved retrieval. (#10818) Thanks @mcinteerj.
|
||||
- Memory/QMD: run boot refresh in background by default, add configurable QMD maintenance timeouts, and retry QMD after fallback failures. (#9690, #9705)
|
||||
- Media understanding: recognize `.caf` audio attachments for transcription. (#10982) Thanks @succ985.
|
||||
- State dir: honor `OPENCLAW_STATE_DIR` for default device identity and canvas storage paths. (#4824) Thanks @kossoy.
|
||||
- Tests: harden flaky hotspots by removing timer sleeps, consolidating onboarding provider-auth coverage, and improving memory test realism. (#11598) Thanks @gumadeiras.
|
||||
|
||||
## 2026.2.6
|
||||
|
||||
### Changes
|
||||
@@ -28,7 +44,6 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- 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.
|
||||
|
||||
@@ -25,9 +25,6 @@ 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!
|
||||
@@ -79,44 +76,3 @@ 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
|
||||
|
||||
We use staged branch promotion to keep `main` stable:
|
||||
|
||||
```
|
||||
dev/* / feature/* / fix/* → develop → alpha → beta → main
|
||||
```
|
||||
|
||||
### For External Contributors
|
||||
|
||||
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
|
||||
|
||||
**Do not target `main`** — PRs to `main` will be redirected to `develop`.
|
||||
|
||||
### For Maintainers
|
||||
|
||||
- **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.
|
||||
|
||||
@@ -23,9 +23,10 @@ It answers you on the channels you already use (WhatsApp, Telegram, Slack, Disco
|
||||
|
||||
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
||||
|
||||
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/start/faq) · [Wizard](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-clawdbot) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
[Website](https://openclaw.ai) · [Docs](https://docs.openclaw.ai) · [DeepWiki](https://deepwiki.com/openclaw/openclaw) · [Getting Started](https://docs.openclaw.ai/start/getting-started) · [Updating](https://docs.openclaw.ai/install/updating) · [Showcase](https://docs.openclaw.ai/start/showcase) · [FAQ](https://docs.openclaw.ai/start/faq) · [Wizard](https://docs.openclaw.ai/start/wizard) · [Nix](https://github.com/openclaw/nix-openclaw) · [Docker](https://docs.openclaw.ai/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
|
||||
Preferred setup: run the onboarding wizard (`openclaw onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||
Preferred setup: run the onboarding wizard (`openclaw onboard`) in your terminal.
|
||||
The wizard guides you step by step through setting up the gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||
Works with npm, pnpm, or bun.
|
||||
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
|
||||
|
||||
|
||||
@@ -1589,6 +1589,140 @@ public struct AgentSummary: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsCreateParams: Codable, Sendable {
|
||||
public let name: String
|
||||
public let workspace: String
|
||||
public let emoji: String?
|
||||
public let avatar: String?
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
workspace: String,
|
||||
emoji: String?,
|
||||
avatar: String?
|
||||
) {
|
||||
self.name = name
|
||||
self.workspace = workspace
|
||||
self.emoji = emoji
|
||||
self.avatar = avatar
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case workspace
|
||||
case emoji
|
||||
case avatar
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsCreateResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let agentid: String
|
||||
public let name: String
|
||||
public let workspace: String
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
agentid: String,
|
||||
name: String,
|
||||
workspace: String
|
||||
) {
|
||||
self.ok = ok
|
||||
self.agentid = agentid
|
||||
self.name = name
|
||||
self.workspace = workspace
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case agentid = "agentId"
|
||||
case name
|
||||
case workspace
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsUpdateParams: Codable, Sendable {
|
||||
public let agentid: String
|
||||
public let name: String?
|
||||
public let workspace: String?
|
||||
public let model: String?
|
||||
public let avatar: String?
|
||||
|
||||
public init(
|
||||
agentid: String,
|
||||
name: String?,
|
||||
workspace: String?,
|
||||
model: String?,
|
||||
avatar: String?
|
||||
) {
|
||||
self.agentid = agentid
|
||||
self.name = name
|
||||
self.workspace = workspace
|
||||
self.model = model
|
||||
self.avatar = avatar
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case agentid = "agentId"
|
||||
case name
|
||||
case workspace
|
||||
case model
|
||||
case avatar
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsUpdateResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let agentid: String
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
agentid: String
|
||||
) {
|
||||
self.ok = ok
|
||||
self.agentid = agentid
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case agentid = "agentId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsDeleteParams: Codable, Sendable {
|
||||
public let agentid: String
|
||||
public let deletefiles: Bool?
|
||||
|
||||
public init(
|
||||
agentid: String,
|
||||
deletefiles: Bool?
|
||||
) {
|
||||
self.agentid = agentid
|
||||
self.deletefiles = deletefiles
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case agentid = "agentId"
|
||||
case deletefiles = "deleteFiles"
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsDeleteResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let agentid: String
|
||||
public let removedbindings: Int
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
agentid: String,
|
||||
removedbindings: Int
|
||||
) {
|
||||
self.ok = ok
|
||||
self.agentid = agentid
|
||||
self.removedbindings = removedbindings
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case agentid = "agentId"
|
||||
case removedbindings = "removedBindings"
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsFileEntry: Codable, Sendable {
|
||||
public let name: String
|
||||
public let path: String
|
||||
|
||||
@@ -1589,6 +1589,140 @@ public struct AgentSummary: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsCreateParams: Codable, Sendable {
|
||||
public let name: String
|
||||
public let workspace: String
|
||||
public let emoji: String?
|
||||
public let avatar: String?
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
workspace: String,
|
||||
emoji: String?,
|
||||
avatar: String?
|
||||
) {
|
||||
self.name = name
|
||||
self.workspace = workspace
|
||||
self.emoji = emoji
|
||||
self.avatar = avatar
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case workspace
|
||||
case emoji
|
||||
case avatar
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsCreateResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let agentid: String
|
||||
public let name: String
|
||||
public let workspace: String
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
agentid: String,
|
||||
name: String,
|
||||
workspace: String
|
||||
) {
|
||||
self.ok = ok
|
||||
self.agentid = agentid
|
||||
self.name = name
|
||||
self.workspace = workspace
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case agentid = "agentId"
|
||||
case name
|
||||
case workspace
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsUpdateParams: Codable, Sendable {
|
||||
public let agentid: String
|
||||
public let name: String?
|
||||
public let workspace: String?
|
||||
public let model: String?
|
||||
public let avatar: String?
|
||||
|
||||
public init(
|
||||
agentid: String,
|
||||
name: String?,
|
||||
workspace: String?,
|
||||
model: String?,
|
||||
avatar: String?
|
||||
) {
|
||||
self.agentid = agentid
|
||||
self.name = name
|
||||
self.workspace = workspace
|
||||
self.model = model
|
||||
self.avatar = avatar
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case agentid = "agentId"
|
||||
case name
|
||||
case workspace
|
||||
case model
|
||||
case avatar
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsUpdateResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let agentid: String
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
agentid: String
|
||||
) {
|
||||
self.ok = ok
|
||||
self.agentid = agentid
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case agentid = "agentId"
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsDeleteParams: Codable, Sendable {
|
||||
public let agentid: String
|
||||
public let deletefiles: Bool?
|
||||
|
||||
public init(
|
||||
agentid: String,
|
||||
deletefiles: Bool?
|
||||
) {
|
||||
self.agentid = agentid
|
||||
self.deletefiles = deletefiles
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case agentid = "agentId"
|
||||
case deletefiles = "deleteFiles"
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsDeleteResult: Codable, Sendable {
|
||||
public let ok: Bool
|
||||
public let agentid: String
|
||||
public let removedbindings: Int
|
||||
|
||||
public init(
|
||||
ok: Bool,
|
||||
agentid: String,
|
||||
removedbindings: Int
|
||||
) {
|
||||
self.ok = ok
|
||||
self.agentid = agentid
|
||||
self.removedbindings = removedbindings
|
||||
}
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case agentid = "agentId"
|
||||
case removedbindings = "removedBindings"
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentsFileEntry: Codable, Sendable {
|
||||
public let name: String
|
||||
public let path: String
|
||||
|
||||
@@ -17,7 +17,7 @@ Hooks are small scripts that run when something happens. There are two kinds:
|
||||
- **Hooks** (this page): run inside the Gateway when agent events fire, like `/new`, `/reset`, `/stop`, or lifecycle events.
|
||||
- **Webhooks**: external HTTP webhooks that let other systems trigger work in OpenClaw. See [Webhook Hooks](/automation/webhook) or use `openclaw webhooks` for Gmail helper commands.
|
||||
|
||||
Hooks can also be bundled inside plugins; see [Plugins](/plugin#plugin-hooks).
|
||||
Hooks can also be bundled inside plugins; see [Plugins](/tools/plugin#plugin-hooks).
|
||||
|
||||
Common uses:
|
||||
|
||||
@@ -18,7 +18,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
||||
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||
- Incoming messages arrive via webhooks; outgoing replies, typing indicators, read receipts, and tapbacks are REST calls.
|
||||
- Attachments and stickers are ingested as inbound media (and surfaced to the agent when possible).
|
||||
- Pairing/allowlist works the same way as other channels (`/start/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
||||
- Pairing/allowlist works the same way as other channels (`/channels/pairing` etc) with `channels.bluebubbles.allowFrom` + pairing codes.
|
||||
- Reactions are surfaced as system events just like Slack/Telegram so agents can "mention" them before replying.
|
||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||
|
||||
@@ -149,7 +149,7 @@ DMs:
|
||||
- Approve via:
|
||||
- `openclaw pairing list bluebubbles`
|
||||
- `openclaw pairing approve bluebubbles <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
||||
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||
|
||||
Groups:
|
||||
|
||||
@@ -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](/plugin) guide.
|
||||
For general channel workflow reference, see [Channels](/channels) and the [Plugins](/tools/plugin) guide.
|
||||
|
||||
@@ -437,6 +437,6 @@ Planned features:
|
||||
|
||||
## See Also
|
||||
|
||||
- [Multi-Agent Configuration](/multi-agent-sandbox-tools)
|
||||
- [Routing Configuration](/concepts/channel-routing)
|
||||
- [Multi-Agent Configuration](/tools/multi-agent-sandbox-tools)
|
||||
- [Routing Configuration](/channels/channel-routing)
|
||||
- [Session Management](/concepts/sessions)
|
||||
@@ -68,7 +68,7 @@ Config:
|
||||
}
|
||||
```
|
||||
|
||||
See: [Broadcast Groups](/broadcast-groups).
|
||||
See: [Broadcast Groups](/channels/broadcast-groups).
|
||||
|
||||
## Config overview
|
||||
|
||||
@@ -371,4 +371,4 @@ The agent system prompt includes a group intro on the first turn of a new group
|
||||
|
||||
## WhatsApp specifics
|
||||
|
||||
See [Group messages](/concepts/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||
See [Group messages](/channels/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||
@@ -224,7 +224,7 @@ DMs:
|
||||
- Approve via:
|
||||
- `openclaw pairing list imessage`
|
||||
- `openclaw pairing approve imessage <CODE>`
|
||||
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
||||
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/channels/pairing)
|
||||
|
||||
Groups:
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Text is supported everywhere; media and reactions vary by channel.
|
||||
- Channels can run simultaneously; configure multiple and OpenClaw will route per chat.
|
||||
- Fastest setup is usually **Telegram** (simple bot token). WhatsApp requires QR pairing and
|
||||
stores more state on disk.
|
||||
- Group behavior varies by channel; see [Groups](/concepts/groups).
|
||||
- Group behavior varies by channel; see [Groups](/channels/groups).
|
||||
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
||||
- Telegram internals: [grammY notes](/channels/grammy).
|
||||
- Troubleshooting: [Channel troubleshooting](/channels/troubleshooting).
|
||||
|
||||
@@ -34,7 +34,7 @@ openclaw plugins install ./extensions/matrix
|
||||
If you choose Matrix during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ openclaw plugins install ./extensions/mattermost
|
||||
If you choose Mattermost during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ openclaw plugins install ./extensions/msteams
|
||||
If you choose Teams during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ openclaw plugins install ./extensions/nextcloud-talk
|
||||
If you choose Nextcloud Talk during configure/onboarding and a git checkout is detected,
|
||||
OpenClaw will offer the local install path automatically.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ DMs:
|
||||
- Approve via:
|
||||
- `openclaw pairing list signal`
|
||||
- `openclaw pairing approve signal <CODE>`
|
||||
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/start/pairing)
|
||||
- Pairing is the default token exchange for Signal DMs. Details: [Pairing](/channels/pairing)
|
||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
||||
|
||||
Groups:
|
||||
|
||||
@@ -351,7 +351,7 @@ Use the global setting when all Telegram bots/accounts should behave the same. U
|
||||
- Approve via:
|
||||
- `openclaw pairing list telegram`
|
||||
- `openclaw pairing approve telegram <CODE>`
|
||||
- Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/start/pairing)
|
||||
- Pairing is the default token exchange used for Telegram DMs. Details: [Pairing](/channels/pairing)
|
||||
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
|
||||
|
||||
#### Finding your Telegram user ID
|
||||
|
||||
@@ -30,7 +30,7 @@ Local checkout (when running from a git repo):
|
||||
openclaw plugins install ./extensions/tlon
|
||||
```
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Local checkout (when running from a git repo):
|
||||
openclaw plugins install ./extensions/twitch
|
||||
```
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Zalo ships as a plugin and is not bundled with the core install.
|
||||
|
||||
- Install via CLI: `openclaw plugins install @openclaw/zalo`
|
||||
- Or select **Zalo** during onboarding and confirm the install prompt
|
||||
- Details: [Plugins](/plugin)
|
||||
- Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup (beginner)
|
||||
|
||||
@@ -104,7 +104,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
||||
- Approve via:
|
||||
- `openclaw pairing list zalo`
|
||||
- `openclaw pairing approve zalo <CODE>`
|
||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
||||
- Pairing is the default token exchange. Details: [Pairing](/channels/pairing)
|
||||
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
||||
|
||||
## Long-polling vs webhook
|
||||
|
||||
@@ -18,7 +18,7 @@ Zalo Personal ships as a plugin and is not bundled with the core install.
|
||||
|
||||
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
|
||||
- Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
|
||||
- Details: [Plugins](/plugin)
|
||||
- Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Prerequisite: zca-cli
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ Manage agent hooks (event-driven automations for commands like `/new`, `/reset`,
|
||||
|
||||
Related:
|
||||
|
||||
- Hooks: [Hooks](/hooks)
|
||||
- Plugin hooks: [Plugins](/plugin#plugin-hooks)
|
||||
- Hooks: [Hooks](/automation/hooks)
|
||||
- Plugin hooks: [Plugins](/tools/plugin#plugin-hooks)
|
||||
|
||||
## List All Hooks
|
||||
|
||||
@@ -248,7 +248,7 @@ openclaw hooks enable session-memory
|
||||
|
||||
**Output:** `~/.openclaw/workspace/memory/YYYY-MM-DD-slug.md`
|
||||
|
||||
**See:** [session-memory documentation](/hooks#session-memory)
|
||||
**See:** [session-memory documentation](/automation/hooks#session-memory)
|
||||
|
||||
### command-logger
|
||||
|
||||
@@ -275,7 +275,7 @@ cat ~/.openclaw/logs/commands.log | jq .
|
||||
grep '"action":"new"' ~/.openclaw/logs/commands.log | jq .
|
||||
```
|
||||
|
||||
**See:** [command-logger documentation](/hooks#command-logger)
|
||||
**See:** [command-logger documentation](/automation/hooks#command-logger)
|
||||
|
||||
### soul-evil
|
||||
|
||||
@@ -301,4 +301,4 @@ Runs `BOOT.md` when the gateway starts (after channels start).
|
||||
openclaw hooks enable boot-md
|
||||
```
|
||||
|
||||
**See:** [boot-md documentation](/hooks#boot-md)
|
||||
**See:** [boot-md documentation](/automation/hooks#boot-md)
|
||||
|
||||
@@ -255,7 +255,7 @@ Manage extensions and their config:
|
||||
- `openclaw plugins enable <id>` / `disable <id>` — toggle `plugins.entries.<id>.enabled`.
|
||||
- `openclaw plugins doctor` — report plugin load errors.
|
||||
|
||||
Most plugin changes require a gateway restart. See [/plugin](/plugin).
|
||||
Most plugin changes require a gateway restart. See [/plugin](/tools/plugin).
|
||||
|
||||
## Memory
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Provided by the active memory plugin (default: `memory-core`; set `plugins.slots
|
||||
Related:
|
||||
|
||||
- Memory concept: [Memory](/concepts/memory)
|
||||
- Plugins: [Plugins](/plugin)
|
||||
- Plugins: [Plugins](/tools/plugin)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Approve or inspect DM pairing requests (for channels that support pairing).
|
||||
|
||||
Related:
|
||||
|
||||
- Pairing flow: [Pairing](/start/pairing)
|
||||
- Pairing flow: [Pairing](/channels/pairing)
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Manage Gateway plugins/extensions (loaded in-process).
|
||||
|
||||
Related:
|
||||
|
||||
- Plugin system: [Plugins](/plugin)
|
||||
- Plugin system: [Plugins](/tools/plugin)
|
||||
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
||||
- Security hardening: [Security](/gateway/security)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Open the terminal UI connected to the Gateway.
|
||||
|
||||
Related:
|
||||
|
||||
- TUI guide: [TUI](/tui)
|
||||
- TUI guide: [TUI](/web/tui)
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ OpenClaw has two hook systems:
|
||||
Use this to add/remove bootstrap context files.
|
||||
- **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).
|
||||
|
||||
See [Hooks](/hooks) for setup and examples.
|
||||
See [Hooks](/automation/hooks) for setup and examples.
|
||||
|
||||
### Plugin hooks (agent + gateway lifecycle)
|
||||
|
||||
@@ -90,7 +90,7 @@ These run inside the agent loop or gateway pipeline:
|
||||
- **`session_start` / `session_end`**: session lifecycle boundaries.
|
||||
- **`gateway_start` / `gateway_stop`**: gateway lifecycle events.
|
||||
|
||||
See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
||||
See [Plugins](/tools/plugin#plugin-hooks) for the hook API and registration details.
|
||||
|
||||
## Streaming + partial replies
|
||||
|
||||
|
||||
@@ -228,6 +228,6 @@ Suggested `.gitignore` starter:
|
||||
## Advanced notes
|
||||
|
||||
- Multi-agent routing can use different workspaces per agent. See
|
||||
[Channel routing](/concepts/channel-routing) for routing configuration.
|
||||
[Channel routing](/channels/channel-routing) for routing configuration.
|
||||
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
|
||||
workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
||||
|
||||
@@ -120,4 +120,4 @@ At minimum, set:
|
||||
|
||||
---
|
||||
|
||||
_Next: [Group Chats](/concepts/group-messages)_ 🦞
|
||||
_Next: [Group Chats](/channels/group-messages)_ 🦞
|
||||
|
||||
@@ -97,7 +97,7 @@ Client Gateway
|
||||
- Gateway auth (`gateway.auth.*`) still applies to **all** connections, local or
|
||||
remote.
|
||||
|
||||
Details: [Gateway protocol](/gateway/protocol), [Pairing](/start/pairing),
|
||||
Details: [Gateway protocol](/gateway/protocol), [Pairing](/channels/pairing),
|
||||
[Security](/gateway/security).
|
||||
|
||||
## Protocol typing and codegen
|
||||
|
||||
@@ -27,7 +27,7 @@ Context is _not the same thing_ as “memory”: memory can be stored on disk an
|
||||
- `/usage tokens` → append per-reply usage footer to normal replies.
|
||||
- `/compact` → summarize older history into a compact entry to free window space.
|
||||
|
||||
See also: [Slash commands](/tools/slash-commands), [Token use & costs](/token-use), [Compaction](/concepts/compaction).
|
||||
See also: [Slash commands](/tools/slash-commands), [Token use & costs](/reference/token-use), [Compaction](/concepts/compaction).
|
||||
|
||||
## Example output
|
||||
|
||||
|
||||
@@ -127,12 +127,18 @@ out to QMD for retrieval. Key points:
|
||||
|
||||
- The gateway writes a self-contained QMD home under
|
||||
`~/.openclaw/agents/<agentId>/qmd/` (config + cache + sqlite DB).
|
||||
- Collections are rewritten from `memory.qmd.paths` (plus default workspace
|
||||
memory files) into `index.yml`, then `qmd update` + `qmd embed` run on boot and
|
||||
on a configurable interval (`memory.qmd.update.interval`, default 5 m).
|
||||
- Collections are created via `qmd collection add` from `memory.qmd.paths`
|
||||
(plus default workspace memory files), then `qmd update` + `qmd embed` run
|
||||
on boot and on a configurable interval (`memory.qmd.update.interval`,
|
||||
default 5 m).
|
||||
- Boot refresh now runs in the background by default so chat startup is not
|
||||
blocked; set `memory.qmd.update.waitForBootSync = true` to keep the previous
|
||||
blocking behavior.
|
||||
- Searches run via `qmd query --json`. If QMD fails or the binary is missing,
|
||||
OpenClaw automatically falls back to the builtin SQLite manager so memory tools
|
||||
keep working.
|
||||
- OpenClaw does not expose QMD embed batch-size tuning today; batch behavior is
|
||||
controlled by QMD itself.
|
||||
- **First search may be slow**: QMD may download local GGUF models (reranker/query
|
||||
expansion) on the first `qmd query` run.
|
||||
- OpenClaw sets `XDG_CONFIG_HOME`/`XDG_CACHE_HOME` automatically when it runs QMD.
|
||||
@@ -170,7 +176,9 @@ out to QMD for retrieval. Key points:
|
||||
stable `name`).
|
||||
- `sessions`: opt into session JSONL indexing (`enabled`, `retentionDays`,
|
||||
`exportDir`).
|
||||
- `update`: controls refresh cadence (`interval`, `debounceMs`, `onBoot`, `embedInterval`).
|
||||
- `update`: controls refresh cadence and maintenance execution:
|
||||
(`interval`, `debounceMs`, `onBoot`, `waitForBootSync`, `embedInterval`,
|
||||
`commandTimeoutMs`, `updateTimeoutMs`, `embedTimeoutMs`).
|
||||
- `limits`: clamp recall payload (`maxResults`, `maxSnippetChars`,
|
||||
`maxInjectedChars`, `timeoutMs`).
|
||||
- `scope`: same schema as [`session.sendPolicy`](/gateway/configuration#session).
|
||||
|
||||
@@ -142,7 +142,7 @@ OpenClaw can expose or hide model reasoning:
|
||||
- Reasoning content still counts toward token usage when produced by the model.
|
||||
- Telegram supports reasoning stream into the draft bubble.
|
||||
|
||||
Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/token-use).
|
||||
Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/reference/token-use).
|
||||
|
||||
## Prefixes, threading, and replies
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ Scan results are ranked by:
|
||||
Input
|
||||
|
||||
- OpenRouter `/models` list (filter `:free`)
|
||||
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [/environment](/environment))
|
||||
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [/environment](/help/environment))
|
||||
- Optional filters: `--max-age-days`, `--min-params`, `--provider`, `--max-candidates`
|
||||
- Probe controls: `--timeout`, `--concurrency`
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ Example:
|
||||
Notes:
|
||||
|
||||
- DM access control is **global per WhatsApp account** (pairing/allowlist), not per agent.
|
||||
- For shared groups, bind the group to one agent or use [Broadcast groups](/broadcast-groups).
|
||||
- For shared groups, bind the group to one agent or use [Broadcast groups](/channels/broadcast-groups).
|
||||
|
||||
## Routing rules (how messages pick an agent)
|
||||
|
||||
@@ -373,4 +373,4 @@ Note: `tools.elevated` is **global** and sender-based; it is not configurable pe
|
||||
If you need per-agent boundaries, use `agents.list[].tools` to deny `exec`.
|
||||
For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent.
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for detailed examples.
|
||||
See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for detailed examples.
|
||||
|
||||
224
docs/docs.json
224
docs/docs.json
@@ -38,18 +38,6 @@
|
||||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/cron",
|
||||
"destination": "/cron-jobs"
|
||||
},
|
||||
{
|
||||
"source": "/model",
|
||||
"destination": "/models"
|
||||
},
|
||||
{
|
||||
"source": "/model/",
|
||||
"destination": "/models"
|
||||
},
|
||||
{
|
||||
"source": "/messages",
|
||||
"destination": "/concepts/messages"
|
||||
@@ -62,6 +50,10 @@
|
||||
"source": "/compaction",
|
||||
"destination": "/concepts/compaction"
|
||||
},
|
||||
{
|
||||
"source": "/cron",
|
||||
"destination": "/cron-jobs"
|
||||
},
|
||||
{
|
||||
"source": "/minimax",
|
||||
"destination": "/providers/minimax"
|
||||
@@ -356,11 +348,11 @@
|
||||
},
|
||||
{
|
||||
"source": "/group-messages",
|
||||
"destination": "/concepts/group-messages"
|
||||
"destination": "/channels/group-messages"
|
||||
},
|
||||
{
|
||||
"source": "/groups",
|
||||
"destination": "/concepts/groups"
|
||||
"destination": "/channels/groups"
|
||||
},
|
||||
{
|
||||
"source": "/health",
|
||||
@@ -486,6 +478,14 @@
|
||||
"source": "/model-failover",
|
||||
"destination": "/concepts/model-failover"
|
||||
},
|
||||
{
|
||||
"source": "/model",
|
||||
"destination": "/models"
|
||||
},
|
||||
{
|
||||
"source": "/model/",
|
||||
"destination": "/models"
|
||||
},
|
||||
{
|
||||
"source": "/models",
|
||||
"destination": "/concepts/models"
|
||||
@@ -508,7 +508,7 @@
|
||||
},
|
||||
{
|
||||
"source": "/pairing",
|
||||
"destination": "/start/pairing"
|
||||
"destination": "/channels/pairing"
|
||||
},
|
||||
{
|
||||
"source": "/plans/cron-add-hardening",
|
||||
@@ -532,11 +532,11 @@
|
||||
},
|
||||
{
|
||||
"source": "/provider-routing",
|
||||
"destination": "/concepts/channel-routing"
|
||||
"destination": "/channels/channel-routing"
|
||||
},
|
||||
{
|
||||
"source": "/concepts/provider-routing",
|
||||
"destination": "/concepts/channel-routing"
|
||||
"destination": "/channels/channel-routing"
|
||||
},
|
||||
{
|
||||
"source": "/queue",
|
||||
@@ -667,8 +667,8 @@
|
||||
"destination": "/help/troubleshooting"
|
||||
},
|
||||
{
|
||||
"source": "/web/tui",
|
||||
"destination": "/tui"
|
||||
"source": "/tui",
|
||||
"destination": "/web/tui"
|
||||
},
|
||||
{
|
||||
"source": "/typebox",
|
||||
@@ -722,9 +722,13 @@
|
||||
"source": "/oauth",
|
||||
"destination": "/concepts/oauth"
|
||||
},
|
||||
{
|
||||
"source": "/plugin",
|
||||
"destination": "/tools/plugin"
|
||||
},
|
||||
{
|
||||
"source": "/plugins",
|
||||
"destination": "/plugin"
|
||||
"destination": "/tools/plugin"
|
||||
},
|
||||
{
|
||||
"source": "/railway",
|
||||
@@ -783,9 +787,17 @@
|
||||
{
|
||||
"tab": "Get started",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Home",
|
||||
"pages": ["index"]
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": ["index", "concepts/features", "start/showcase"]
|
||||
"pages": ["start/showcase"]
|
||||
},
|
||||
{
|
||||
"group": "Core concepts",
|
||||
"pages": ["concepts/features"]
|
||||
},
|
||||
{
|
||||
"group": "First steps",
|
||||
@@ -861,11 +873,11 @@
|
||||
{
|
||||
"group": "Configuration",
|
||||
"pages": [
|
||||
"start/pairing",
|
||||
"concepts/group-messages",
|
||||
"concepts/groups",
|
||||
"broadcast-groups",
|
||||
"concepts/channel-routing",
|
||||
"channels/pairing",
|
||||
"channels/group-messages",
|
||||
"channels/groups",
|
||||
"channels/broadcast-groups",
|
||||
"channels/channel-routing",
|
||||
"channels/location",
|
||||
"channels/troubleshooting"
|
||||
]
|
||||
@@ -884,10 +896,13 @@
|
||||
"concepts/system-prompt",
|
||||
"concepts/context",
|
||||
"concepts/agent-workspace",
|
||||
"start/bootstrapping",
|
||||
"concepts/oauth"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Bootstrapping",
|
||||
"pages": ["start/bootstrapping"]
|
||||
},
|
||||
{
|
||||
"group": "Sessions and memory",
|
||||
"pages": [
|
||||
@@ -945,25 +960,26 @@
|
||||
},
|
||||
{
|
||||
"group": "Agent coordination",
|
||||
"pages": ["tools/agent-send", "tools/subagents", "multi-agent-sandbox-tools"]
|
||||
"pages": ["tools/agent-send", "tools/subagents", "tools/multi-agent-sandbox-tools"]
|
||||
},
|
||||
{
|
||||
"group": "Skills and extensions",
|
||||
"group": "Skills",
|
||||
"pages": [
|
||||
"tools/slash-commands",
|
||||
"tools/skills",
|
||||
"tools/skills-config",
|
||||
"tools/clawhub",
|
||||
"plugin",
|
||||
"plugins/voice-call",
|
||||
"plugins/zalouser"
|
||||
"tools/plugin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Extensions",
|
||||
"pages": ["plugins/voice-call", "plugins/zalouser"]
|
||||
},
|
||||
{
|
||||
"group": "Automation",
|
||||
"pages": [
|
||||
"hooks",
|
||||
"hooks/soul-evil",
|
||||
"automation/hooks",
|
||||
"automation/cron-jobs",
|
||||
"automation/cron-vs-heartbeat",
|
||||
"automation/troubleshooting",
|
||||
@@ -973,6 +989,10 @@
|
||||
"automation/auth-monitoring"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Hooks",
|
||||
"pages": ["hooks/soul-evil"]
|
||||
},
|
||||
{
|
||||
"group": "Media and devices",
|
||||
"pages": [
|
||||
@@ -993,7 +1013,11 @@
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": ["providers/index", "providers/models", "concepts/models"]
|
||||
"pages": ["providers/index", "providers/models"]
|
||||
},
|
||||
{
|
||||
"group": "Model concepts",
|
||||
"pages": ["concepts/models"]
|
||||
},
|
||||
{
|
||||
"group": "Configuration",
|
||||
@@ -1005,7 +1029,7 @@
|
||||
"providers/anthropic",
|
||||
"providers/openai",
|
||||
"providers/openrouter",
|
||||
"bedrock",
|
||||
"providers/bedrock",
|
||||
"providers/vercel-ai-gateway",
|
||||
"providers/moonshot",
|
||||
"providers/minimax",
|
||||
@@ -1120,7 +1144,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Web interfaces",
|
||||
"pages": ["web/index", "web/control-ui", "web/dashboard", "web/webchat", "tui"]
|
||||
"pages": ["web/index", "web/control-ui", "web/dashboard", "web/webchat", "web/tui"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1188,14 +1212,16 @@
|
||||
},
|
||||
{
|
||||
"group": "Technical reference",
|
||||
"pages": ["reference/wizard", "reference/token-use"]
|
||||
},
|
||||
{
|
||||
"group": "Concept internals",
|
||||
"pages": [
|
||||
"reference/wizard",
|
||||
"concepts/typebox",
|
||||
"concepts/markdown-formatting",
|
||||
"concepts/typing-indicators",
|
||||
"concepts/usage-tracking",
|
||||
"concepts/timezone",
|
||||
"token-use"
|
||||
"concepts/timezone"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1221,18 +1247,23 @@
|
||||
},
|
||||
{
|
||||
"group": "Environment and debugging",
|
||||
"pages": [
|
||||
"install/node",
|
||||
"environment",
|
||||
"debugging",
|
||||
"testing",
|
||||
"scripts",
|
||||
"reference/session-management-compaction"
|
||||
]
|
||||
"pages": ["help/environment", "help/debugging", "help/testing", "help/scripts"]
|
||||
},
|
||||
{
|
||||
"group": "Developer workflows",
|
||||
"pages": ["start/setup", "help/submitting-a-pr", "help/submitting-an-issue"]
|
||||
"group": "Node runtime",
|
||||
"pages": ["install/node"]
|
||||
},
|
||||
{
|
||||
"group": "Compaction internals",
|
||||
"pages": ["reference/session-management-compaction"]
|
||||
},
|
||||
{
|
||||
"group": "Developer setup",
|
||||
"pages": ["start/setup"]
|
||||
},
|
||||
{
|
||||
"group": "Contributing",
|
||||
"pages": ["help/submitting-a-pr", "help/submitting-an-issue"]
|
||||
},
|
||||
{
|
||||
"group": "Docs meta",
|
||||
@@ -1248,9 +1279,17 @@
|
||||
{
|
||||
"tab": "快速开始",
|
||||
"groups": [
|
||||
{
|
||||
"group": "首页",
|
||||
"pages": ["zh-CN/index"]
|
||||
},
|
||||
{
|
||||
"group": "概览",
|
||||
"pages": ["zh-CN/index", "zh-CN/concepts/features", "zh-CN/start/showcase"]
|
||||
"pages": ["zh-CN/start/showcase"]
|
||||
},
|
||||
{
|
||||
"group": "核心概念",
|
||||
"pages": ["zh-CN/concepts/features"]
|
||||
},
|
||||
{
|
||||
"group": "第一步",
|
||||
@@ -1276,7 +1315,6 @@
|
||||
{
|
||||
"group": "安装方式",
|
||||
"pages": [
|
||||
"zh-CN/install/node",
|
||||
"zh-CN/install/docker",
|
||||
"zh-CN/install/nix",
|
||||
"zh-CN/install/ansible",
|
||||
@@ -1340,11 +1378,11 @@
|
||||
{
|
||||
"group": "配置",
|
||||
"pages": [
|
||||
"zh-CN/start/pairing",
|
||||
"zh-CN/concepts/group-messages",
|
||||
"zh-CN/concepts/groups",
|
||||
"zh-CN/broadcast-groups",
|
||||
"zh-CN/concepts/channel-routing",
|
||||
"zh-CN/channels/pairing",
|
||||
"zh-CN/channels/group-messages",
|
||||
"zh-CN/channels/groups",
|
||||
"zh-CN/channels/broadcast-groups",
|
||||
"zh-CN/channels/channel-routing",
|
||||
"zh-CN/channels/location",
|
||||
"zh-CN/channels/troubleshooting"
|
||||
]
|
||||
@@ -1366,6 +1404,10 @@
|
||||
"zh-CN/concepts/oauth"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "引导",
|
||||
"pages": ["zh-CN/start/bootstrapping"]
|
||||
},
|
||||
{
|
||||
"group": "会话与记忆",
|
||||
"pages": [
|
||||
@@ -1426,38 +1468,45 @@
|
||||
"pages": [
|
||||
"zh-CN/tools/agent-send",
|
||||
"zh-CN/tools/subagents",
|
||||
"zh-CN/multi-agent-sandbox-tools"
|
||||
"zh-CN/tools/multi-agent-sandbox-tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "技能与扩展",
|
||||
"group": "技能",
|
||||
"pages": [
|
||||
"zh-CN/tools/slash-commands",
|
||||
"zh-CN/tools/skills",
|
||||
"zh-CN/tools/skills-config",
|
||||
"zh-CN/tools/clawhub",
|
||||
"zh-CN/plugin",
|
||||
"zh-CN/plugins/voice-call",
|
||||
"zh-CN/plugins/zalouser"
|
||||
"zh-CN/tools/plugin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "扩展",
|
||||
"pages": ["zh-CN/plugins/voice-call", "zh-CN/plugins/zalouser"]
|
||||
},
|
||||
{
|
||||
"group": "自动化",
|
||||
"pages": [
|
||||
"zh-CN/hooks",
|
||||
"zh-CN/hooks/soul-evil",
|
||||
"zh-CN/automation/hooks",
|
||||
"zh-CN/automation/cron-jobs",
|
||||
"zh-CN/automation/cron-vs-heartbeat",
|
||||
"zh-CN/automation/troubleshooting",
|
||||
"zh-CN/automation/webhook",
|
||||
"zh-CN/automation/gmail-pubsub",
|
||||
"zh-CN/automation/poll",
|
||||
"zh-CN/automation/auth-monitoring"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Hooks",
|
||||
"pages": ["zh-CN/hooks/soul-evil"]
|
||||
},
|
||||
{
|
||||
"group": "媒体与设备",
|
||||
"pages": [
|
||||
"zh-CN/nodes/index",
|
||||
"zh-CN/nodes/troubleshooting",
|
||||
"zh-CN/nodes/images",
|
||||
"zh-CN/nodes/audio",
|
||||
"zh-CN/nodes/camera",
|
||||
@@ -1473,11 +1522,11 @@
|
||||
"groups": [
|
||||
{
|
||||
"group": "概览",
|
||||
"pages": [
|
||||
"zh-CN/providers/index",
|
||||
"zh-CN/providers/models",
|
||||
"zh-CN/concepts/models"
|
||||
]
|
||||
"pages": ["zh-CN/providers/index", "zh-CN/providers/models"]
|
||||
},
|
||||
{
|
||||
"group": "模型概念",
|
||||
"pages": ["zh-CN/concepts/models"]
|
||||
},
|
||||
{
|
||||
"group": "配置",
|
||||
@@ -1489,14 +1538,15 @@
|
||||
"zh-CN/providers/anthropic",
|
||||
"zh-CN/providers/openai",
|
||||
"zh-CN/providers/openrouter",
|
||||
"zh-CN/bedrock",
|
||||
"zh-CN/providers/bedrock",
|
||||
"zh-CN/providers/vercel-ai-gateway",
|
||||
"zh-CN/providers/moonshot",
|
||||
"zh-CN/providers/minimax",
|
||||
"zh-CN/providers/opencode",
|
||||
"zh-CN/providers/glm",
|
||||
"zh-CN/providers/zai",
|
||||
"zh-CN/providers/synthetic"
|
||||
"zh-CN/providers/synthetic",
|
||||
"zh-CN/providers/qianfan"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1612,7 +1662,7 @@
|
||||
"zh-CN/web/control-ui",
|
||||
"zh-CN/web/dashboard",
|
||||
"zh-CN/web/webchat",
|
||||
"zh-CN/tui"
|
||||
"zh-CN/web/tui"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1681,13 +1731,16 @@
|
||||
},
|
||||
{
|
||||
"group": "技术参考",
|
||||
"pages": ["zh-CN/reference/wizard", "zh-CN/reference/token-use"]
|
||||
},
|
||||
{
|
||||
"group": "概念内部机制",
|
||||
"pages": [
|
||||
"zh-CN/concepts/typebox",
|
||||
"zh-CN/concepts/markdown-formatting",
|
||||
"zh-CN/concepts/typing-indicators",
|
||||
"zh-CN/concepts/usage-tracking",
|
||||
"zh-CN/concepts/timezone",
|
||||
"zh-CN/token-use"
|
||||
"zh-CN/concepts/timezone"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1714,17 +1767,28 @@
|
||||
{
|
||||
"group": "环境与调试",
|
||||
"pages": [
|
||||
"zh-CN/environment",
|
||||
"zh-CN/debugging",
|
||||
"zh-CN/testing",
|
||||
"zh-CN/scripts",
|
||||
"zh-CN/reference/session-management-compaction"
|
||||
"zh-CN/help/environment",
|
||||
"zh-CN/help/debugging",
|
||||
"zh-CN/help/testing",
|
||||
"zh-CN/help/scripts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "开发者工作流",
|
||||
"group": "Node 运行时",
|
||||
"pages": ["zh-CN/install/node"]
|
||||
},
|
||||
{
|
||||
"group": "压缩机制内部参考",
|
||||
"pages": ["zh-CN/reference/session-management-compaction"]
|
||||
},
|
||||
{
|
||||
"group": "开发者设置",
|
||||
"pages": ["zh-CN/start/setup"]
|
||||
},
|
||||
{
|
||||
"group": "贡献",
|
||||
"pages": ["zh-CN/help/submitting-a-pr", "zh-CN/help/submitting-an-issue"]
|
||||
},
|
||||
{
|
||||
"group": "文档元信息",
|
||||
"pages": ["zh-CN/start/hubs", "zh-CN/start/docs-directory"]
|
||||
|
||||
@@ -36,5 +36,5 @@ false negatives when deciding whether to respond in DMs or groups.
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Group Chats](/concepts/groups)
|
||||
- [Group Chats](/channels/groups)
|
||||
- [Telegram Provider](/channels/telegram)
|
||||
|
||||
@@ -289,7 +289,7 @@ process env is missing the key (same non-overriding rule):
|
||||
}
|
||||
```
|
||||
|
||||
See [/environment](/environment) for full precedence and sources.
|
||||
See [/environment](/help/environment) for full precedence and sources.
|
||||
|
||||
### `env.shellEnv` (optional)
|
||||
|
||||
@@ -788,7 +788,7 @@ levels in one gateway:
|
||||
- **Read-only** tools + workspace
|
||||
- **No filesystem access** (messaging/session tools only)
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence and
|
||||
See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for precedence and
|
||||
additional examples.
|
||||
|
||||
Full access (no sandbox):
|
||||
@@ -2857,7 +2857,7 @@ Example:
|
||||
Controls plugin discovery, allow/deny, and per-plugin config. Plugins are loaded
|
||||
from `~/.openclaw/extensions`, `<workspace>/.openclaw/extensions`, plus any
|
||||
`plugins.load.paths` entries. **Config changes require a gateway restart.**
|
||||
See [/plugin](/plugin) for full usage.
|
||||
See [/plugin](/tools/plugin) for full usage.
|
||||
|
||||
Fields:
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ Use `accountId` to target a specific account on multi-account channels like Tele
|
||||
- `session`: optional session key for heartbeat runs.
|
||||
- `main` (default): agent main session.
|
||||
- Explicit session key (copy from `openclaw sessions --json` or the [sessions CLI](/cli/sessions)).
|
||||
- Session key formats: see [Sessions](/concepts/session) and [Groups](/concepts/groups).
|
||||
- Session key formats: see [Sessions](/concepts/session) and [Groups](/channels/groups).
|
||||
- `target`:
|
||||
- `last` (default): deliver to the last used external channel.
|
||||
- explicit channel: `whatsapp` / `telegram` / `discord` / `googlechat` / `slack` / `msteams` / `signal` / `imessage`.
|
||||
|
||||
@@ -168,7 +168,7 @@ Debugging:
|
||||
|
||||
Each agent can override sandbox + tools:
|
||||
`agents.list[].sandbox` and `agents.list[].tools` (plus `agents.list[].tools.sandbox.tools` for sandbox tool policy).
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence.
|
||||
See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for precedence.
|
||||
|
||||
## Minimal enable example
|
||||
|
||||
@@ -189,5 +189,5 @@ See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence.
|
||||
## Related docs
|
||||
|
||||
- [Sandbox Configuration](/gateway/configuration#agentsdefaults-sandbox)
|
||||
- [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools)
|
||||
- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools)
|
||||
- [Security](/gateway/security)
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
---
|
||||
title: Formal Verification (Security Models)
|
||||
summary: Machine-checked security models for OpenClaw’s highest-risk paths.
|
||||
permalink: /security/formal-verification/
|
||||
---
|
||||
|
||||
# Formal Verification (Security Models)
|
||||
|
||||
This page tracks OpenClaw’s **formal security models** (TLA+/TLC today; more as needed).
|
||||
|
||||
> Note: some older links may refer to the previous project name.
|
||||
|
||||
**Goal (north star):** provide a machine-checked argument that OpenClaw enforces its
|
||||
intended security policy (authorization, session isolation, tool gating, and
|
||||
misconfiguration safety), under explicit assumptions.
|
||||
|
||||
**What this is (today):** an executable, attacker-driven **security regression suite**:
|
||||
|
||||
- Each claim has a runnable model-check over a finite state space.
|
||||
- Many claims have a paired **negative model** that produces a counterexample trace for a realistic bug class.
|
||||
|
||||
**What this is not (yet):** a proof that “OpenClaw is secure in all respects” or that the full TypeScript implementation is correct.
|
||||
|
||||
## Where the models live
|
||||
|
||||
Models are maintained in a separate repo: [vignesh07/clawdbot-formal-models](https://github.com/vignesh07/clawdbot-formal-models).
|
||||
|
||||
## Important caveats
|
||||
|
||||
- These are **models**, not the full TypeScript implementation. Drift between model and code is possible.
|
||||
- Results are bounded by the state space explored by TLC; “green” does not imply security beyond the modeled assumptions and bounds.
|
||||
- Some claims rely on explicit environmental assumptions (e.g., correct deployment, correct configuration inputs).
|
||||
|
||||
## Reproducing results
|
||||
|
||||
Today, results are reproduced by cloning the models repo locally and running TLC (see below). A future iteration could offer:
|
||||
|
||||
- CI-run models with public artifacts (counterexample traces, run logs)
|
||||
- a hosted “run this model” workflow for small, bounded checks
|
||||
|
||||
Getting started:
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
make <target>
|
||||
```
|
||||
|
||||
### Gateway exposure and open gateway misconfiguration
|
||||
|
||||
**Claim:** binding beyond loopback without auth can make remote compromise possible / increases exposure; token/password blocks unauth attackers (per the model assumptions).
|
||||
|
||||
- Green runs:
|
||||
- `make gateway-exposure-v2`
|
||||
- `make gateway-exposure-v2-protected`
|
||||
- Red (expected):
|
||||
- `make gateway-exposure-v2-negative`
|
||||
|
||||
See also: `docs/gateway-exposure-matrix.md` in the models repo.
|
||||
|
||||
### Nodes.run pipeline (highest-risk capability)
|
||||
|
||||
**Claim:** `nodes.run` requires (a) node command allowlist plus declared commands and (b) live approval when configured; approvals are tokenized to prevent replay (in the model).
|
||||
|
||||
- Green runs:
|
||||
- `make nodes-pipeline`
|
||||
- `make approvals-token`
|
||||
- Red (expected):
|
||||
- `make nodes-pipeline-negative`
|
||||
- `make approvals-token-negative`
|
||||
|
||||
### Pairing store (DM gating)
|
||||
|
||||
**Claim:** pairing requests respect TTL and pending-request caps.
|
||||
|
||||
- Green runs:
|
||||
- `make pairing`
|
||||
- `make pairing-cap`
|
||||
- Red (expected):
|
||||
- `make pairing-negative`
|
||||
- `make pairing-cap-negative`
|
||||
|
||||
### Ingress gating (mentions + control-command bypass)
|
||||
|
||||
**Claim:** in group contexts requiring mention, an unauthorized “control command” cannot bypass mention gating.
|
||||
|
||||
- Green:
|
||||
- `make ingress-gating`
|
||||
- Red (expected):
|
||||
- `make ingress-gating-negative`
|
||||
|
||||
### Routing/session-key isolation
|
||||
|
||||
**Claim:** DMs from distinct peers do not collapse into the same session unless explicitly linked/configured.
|
||||
|
||||
- Green:
|
||||
- `make routing-isolation`
|
||||
- Red (expected):
|
||||
- `make routing-isolation-negative`
|
||||
|
||||
## v1++: additional bounded models (concurrency, retries, trace correctness)
|
||||
|
||||
These are follow-on models that tighten fidelity around real-world failure modes (non-atomic updates, retries, and message fan-out).
|
||||
|
||||
### Pairing store concurrency / idempotency
|
||||
|
||||
**Claim:** a pairing store should enforce `MaxPending` and idempotency even under interleavings (i.e., “check-then-write” must be atomic / locked; refresh shouldn’t create duplicates).
|
||||
|
||||
What it means:
|
||||
|
||||
- Under concurrent requests, you can’t exceed `MaxPending` for a channel.
|
||||
- Repeated requests/refreshes for the same `(channel, sender)` should not create duplicate live pending rows.
|
||||
|
||||
- Green runs:
|
||||
- `make pairing-race` (atomic/locked cap check)
|
||||
- `make pairing-idempotency`
|
||||
- `make pairing-refresh`
|
||||
- `make pairing-refresh-race`
|
||||
- Red (expected):
|
||||
- `make pairing-race-negative` (non-atomic begin/commit cap race)
|
||||
- `make pairing-idempotency-negative`
|
||||
- `make pairing-refresh-negative`
|
||||
- `make pairing-refresh-race-negative`
|
||||
|
||||
### Ingress trace correlation / idempotency
|
||||
|
||||
**Claim:** ingestion should preserve trace correlation across fan-out and be idempotent under provider retries.
|
||||
|
||||
What it means:
|
||||
|
||||
- When one external event becomes multiple internal messages, every part keeps the same trace/event identity.
|
||||
- Retries do not result in double-processing.
|
||||
- If provider event IDs are missing, dedupe falls back to a safe key (e.g., trace ID) to avoid dropping distinct events.
|
||||
|
||||
- Green:
|
||||
- `make ingress-trace`
|
||||
- `make ingress-trace2`
|
||||
- `make ingress-idempotency`
|
||||
- `make ingress-dedupe-fallback`
|
||||
- Red (expected):
|
||||
- `make ingress-trace-negative`
|
||||
- `make ingress-trace2-negative`
|
||||
- `make ingress-idempotency-negative`
|
||||
- `make ingress-dedupe-fallback-negative`
|
||||
|
||||
### Routing dmScope precedence + identityLinks
|
||||
|
||||
**Claim:** routing must keep DM sessions isolated by default, and only collapse sessions when explicitly configured (channel precedence + identity links).
|
||||
|
||||
What it means:
|
||||
|
||||
- Channel-specific dmScope overrides must win over global defaults.
|
||||
- identityLinks should collapse only within explicit linked groups, not across unrelated peers.
|
||||
|
||||
- Green:
|
||||
- `make routing-precedence`
|
||||
- `make routing-identitylinks`
|
||||
- Red (expected):
|
||||
- `make routing-precedence-negative`
|
||||
- `make routing-identitylinks-negative`
|
||||
@@ -175,7 +175,7 @@ Plugins run **in-process** with the Gateway. Treat them as trusted code:
|
||||
- OpenClaw uses `npm pack` and then runs `npm install --omit=dev` in that directory (npm lifecycle scripts can execute code during install).
|
||||
- Prefer pinned, exact versions (`@scope/pkg@1.2.3`), and inspect the unpacked code on disk before enabling.
|
||||
|
||||
Details: [Plugins](/plugin)
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## DM access model (pairing / allowlist / open / disabled)
|
||||
|
||||
@@ -193,7 +193,7 @@ openclaw pairing list <channel>
|
||||
openclaw pairing approve <channel> <code>
|
||||
```
|
||||
|
||||
Details + files on disk: [Pairing](/start/pairing)
|
||||
Details + files on disk: [Pairing](/channels/pairing)
|
||||
|
||||
## DM session isolation (multi-user mode)
|
||||
|
||||
@@ -229,7 +229,7 @@ OpenClaw has two separate “who can trigger me?” layers:
|
||||
- `channels.discord.guilds` / `channels.slack.channels`: per-surface allowlists + mention defaults.
|
||||
- **Security note:** treat `dmPolicy="open"` and `groupPolicy="open"` as last-resort settings. They should be barely used; prefer pairing + allowlists unless you fully trust every member of the room.
|
||||
|
||||
Details: [Configuration](/gateway/configuration) and [Groups](/concepts/groups)
|
||||
Details: [Configuration](/gateway/configuration) and [Groups](/channels/groups)
|
||||
|
||||
## Prompt injection (what it is, why it matters)
|
||||
|
||||
@@ -627,7 +627,7 @@ access those accounts and data. Treat browser profiles as **sensitive state**:
|
||||
|
||||
With multi-agent routing, each agent can have its own sandbox + tool policy:
|
||||
use this to give **full access**, **read-only**, or **no access** per agent.
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for full details
|
||||
See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for full details
|
||||
and precedence rules.
|
||||
|
||||
Common use cases:
|
||||
|
||||
@@ -56,8 +56,8 @@ Common signatures:
|
||||
Related:
|
||||
|
||||
- [/channels/troubleshooting](/channels/troubleshooting)
|
||||
- [/start/pairing](/start/pairing)
|
||||
- [/concepts/groups](/concepts/groups)
|
||||
- [/channels/pairing](/channels/pairing)
|
||||
- [/channels/groups](/channels/groups)
|
||||
|
||||
## Dashboard control ui connectivity
|
||||
|
||||
|
||||
@@ -707,7 +707,7 @@ See [Models](/cli/models) and [OAuth](/concepts/oauth).
|
||||
|
||||
### Is AWS Bedrock supported
|
||||
|
||||
Yes - via pi-ai's **Amazon Bedrock (Converse)** provider with **manual config**. You must supply AWS credentials/region on the gateway host and add a Bedrock provider entry in your models config. See [Amazon Bedrock](/bedrock) and [Model providers](/providers/models). If you prefer a managed key flow, an OpenAI-compatible proxy in front of Bedrock is still a valid option.
|
||||
Yes - via pi-ai's **Amazon Bedrock (Converse)** provider with **manual config**. You must supply AWS credentials/region on the gateway host and add a Bedrock provider entry in your models config. See [Amazon Bedrock](/providers/bedrock) and [Model providers](/providers/models). If you prefer a managed key flow, an OpenAI-compatible proxy in front of Bedrock is still a valid option.
|
||||
|
||||
### How does Codex auth work
|
||||
|
||||
@@ -1177,7 +1177,7 @@ Yes - if your private traffic is **DMs** and your public traffic is **groups**.
|
||||
|
||||
Use `agents.defaults.sandbox.mode: "non-main"` so group/channel sessions (non-main keys) run in Docker, while the main DM session stays on-host. Then restrict what tools are available in sandboxed sessions via `tools.sandbox.tools`.
|
||||
|
||||
Setup walkthrough + example config: [Groups: personal DMs + public groups](/concepts/groups#pattern-personal-dms-public-groups-single-agent)
|
||||
Setup walkthrough + example config: [Groups: personal DMs + public groups](/channels/groups#pattern-personal-dms-public-groups-single-agent)
|
||||
|
||||
Key config reference: [Gateway configuration](/gateway/configuration#agentsdefaultssandbox)
|
||||
|
||||
@@ -1427,7 +1427,7 @@ The common pattern is **one Gateway** (e.g. Raspberry Pi) plus **nodes** and **a
|
||||
- **Sub-agents:** spawn background work from a main agent when you want parallelism.
|
||||
- **TUI:** connect to the Gateway and switch agents/sessions.
|
||||
|
||||
Docs: [Nodes](/nodes), [Remote access](/gateway/remote), [Multi-Agent Routing](/concepts/multi-agent), [Sub-agents](/tools/subagents), [TUI](/tui).
|
||||
Docs: [Nodes](/nodes), [Remote access](/gateway/remote), [Multi-Agent Routing](/concepts/multi-agent), [Sub-agents](/tools/subagents), [TUI](/web/tui).
|
||||
|
||||
### Can the OpenClaw browser run headless
|
||||
|
||||
@@ -1681,7 +1681,7 @@ You can also define inline env vars in config (applied only if missing from the
|
||||
}
|
||||
```
|
||||
|
||||
See [/environment](/environment) for full precedence and sources.
|
||||
See [/environment](/help/environment) for full precedence and sources.
|
||||
|
||||
### I started the Gateway via the service and my env vars disappeared What now
|
||||
|
||||
@@ -1729,7 +1729,7 @@ openclaw models status
|
||||
```
|
||||
|
||||
Copilot tokens are read from `COPILOT_GITHUB_TOKEN` (also `GH_TOKEN` / `GITHUB_TOKEN`).
|
||||
See [/concepts/model-providers](/concepts/model-providers) and [/environment](/environment).
|
||||
See [/concepts/model-providers](/concepts/model-providers) and [/environment](/help/environment).
|
||||
|
||||
## Sessions and multiple chats
|
||||
|
||||
@@ -1902,11 +1902,11 @@ Two common causes:
|
||||
- Mention gating is on (default). You must @mention the bot (or match `mentionPatterns`).
|
||||
- You configured `channels.whatsapp.groups` without `"*"` and the group isn't allowlisted.
|
||||
|
||||
See [Groups](/concepts/groups) and [Group messages](/concepts/group-messages).
|
||||
See [Groups](/channels/groups) and [Group messages](/channels/group-messages).
|
||||
|
||||
### Do groupsthreads share context with DMs
|
||||
|
||||
Direct chats collapse to the main session by default. Groups/channels have their own session keys, and Telegram topics / Discord threads are separate sessions. See [Groups](/concepts/groups) and [Group messages](/concepts/group-messages).
|
||||
Direct chats collapse to the main session by default. Groups/channels have their own session keys, and Telegram topics / Discord threads are separate sessions. See [Groups](/channels/groups) and [Group messages](/channels/group-messages).
|
||||
|
||||
### How many workspaces and agents can I create
|
||||
|
||||
@@ -1994,7 +1994,7 @@ Safe options:
|
||||
|
||||
- `/model` in chat (quick, per-session)
|
||||
- `openclaw models set ...` (updates just model config)
|
||||
- `openclaw configure --section models` (interactive)
|
||||
- `openclaw configure --section model` (interactive)
|
||||
- edit `agents.defaults.model` in `~/.openclaw/openclaw.json`
|
||||
|
||||
Avoid `config.apply` with a partial object unless you intend to replace the whole config.
|
||||
@@ -2609,7 +2609,7 @@ openclaw logs --follow
|
||||
In the TUI, use `/status` to see the current state. If you expect replies in a chat
|
||||
channel, make sure delivery is enabled (`/deliver on`).
|
||||
|
||||
Docs: [TUI](/tui), [Slash commands](/tools/slash-commands).
|
||||
Docs: [TUI](/web/tui), [Slash commands](/tools/slash-commands).
|
||||
|
||||
### How do I completely stop then start the Gateway
|
||||
|
||||
@@ -2701,7 +2701,7 @@ credentials or revoke access without impacting your personal accounts.
|
||||
Start small. Give access only to the tools and accounts you actually need, and expand
|
||||
later if required.
|
||||
|
||||
Docs: [Security](/gateway/security), [Pairing](/start/pairing).
|
||||
Docs: [Security](/gateway/security), [Pairing](/channels/pairing).
|
||||
|
||||
### Can I give it autonomy over my text messages and is that safe
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ flowchart TD
|
||||
|
||||
- [/gateway/troubleshooting#no-replies](/gateway/troubleshooting#no-replies)
|
||||
- [/channels/troubleshooting](/channels/troubleshooting)
|
||||
- [/start/pairing](/start/pairing)
|
||||
- [/channels/pairing](/channels/pairing)
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
@@ -66,4 +66,4 @@ Create `SOUL_EVIL.md` in the agent workspace root (next to `SOUL.md`).
|
||||
|
||||
## See Also
|
||||
|
||||
- [Hooks](/hooks)
|
||||
- [Hooks](/automation/hooks)
|
||||
|
||||
@@ -107,7 +107,7 @@ Should show **only port 22** (SSH) open. All other services (gateway, Docker) ar
|
||||
|
||||
Docker is installed for **agent sandboxes** (isolated tool execution), not for running the gateway itself. The gateway binds to localhost only and is accessible via Tailscale VPN.
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for sandbox configuration.
|
||||
See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for sandbox configuration.
|
||||
|
||||
## Manual Installation
|
||||
|
||||
@@ -205,4 +205,4 @@ For detailed security architecture and troubleshooting:
|
||||
- [openclaw-ansible](https://github.com/openclaw/openclaw-ansible) — full deployment guide
|
||||
- [Docker](/install/docker) — containerized gateway setup
|
||||
- [Sandboxing](/gateway/sandboxing) — agent sandbox configuration
|
||||
- [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) — per-agent isolation
|
||||
- [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) — per-agent isolation
|
||||
|
||||
@@ -336,7 +336,7 @@ mixed access levels in one gateway:
|
||||
- Read-only tools + read-only workspace (family/work agent)
|
||||
- No filesystem/shell tools (public agent)
|
||||
|
||||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for examples,
|
||||
See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for examples,
|
||||
precedence, and troubleshooting.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -21,7 +21,7 @@ devices across localhost, LAN, and tailnet.
|
||||
|
||||
## Pairing + identity
|
||||
|
||||
- [Pairing overview (DM + nodes)](/start/pairing)
|
||||
- [Pairing overview (DM + nodes)](/channels/pairing)
|
||||
- [Gateway-owned node pairing](/gateway/pairing)
|
||||
- [Devices CLI (pairing + token rotation)](/cli/devices)
|
||||
- [Pairing CLI (DM approvals)](/cli/pairing)
|
||||
|
||||
@@ -13,7 +13,7 @@ OpenClaw uses this manifest to validate configuration **without executing plugin
|
||||
code**. Missing or invalid manifests are treated as plugin errors and block
|
||||
config validation.
|
||||
|
||||
See the full plugin system guide: [Plugins](/plugin).
|
||||
See the full plugin system guide: [Plugins](/tools/plugin).
|
||||
|
||||
## Required fields
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Restart the Gateway after enabling the plugin.
|
||||
|
||||
Dev/local checkout: `openclaw plugins install ./extensions/open-prose`
|
||||
|
||||
Related docs: [Plugins](/plugin), [Plugin manifest](/plugins/manifest), [Skills](/tools/skills).
|
||||
Related docs: [Plugins](/tools/plugin), [Plugin manifest](/plugins/manifest), [Skills](/tools/skills).
|
||||
|
||||
## Slash command
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ See [Venice AI](/providers/venice).
|
||||
- [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
|
||||
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
|
||||
- [OpenCode Zen](/providers/opencode)
|
||||
- [Amazon Bedrock](/bedrock)
|
||||
- [Amazon Bedrock](/providers/bedrock)
|
||||
- [Z.AI](/providers/zai)
|
||||
- [Xiaomi](/providers/xiaomi)
|
||||
- [GLM models](/providers/glm)
|
||||
|
||||
@@ -45,7 +45,7 @@ See [Venice AI](/providers/venice).
|
||||
- [GLM models](/providers/glm)
|
||||
- [MiniMax](/providers/minimax)
|
||||
- [Venice (Venice AI)](/providers/venice)
|
||||
- [Amazon Bedrock](/bedrock)
|
||||
- [Amazon Bedrock](/providers/bedrock)
|
||||
- [Qianfan](/providers/qianfan)
|
||||
|
||||
For the full provider catalog (xAI, Groq, Mistral, etc.) and advanced configuration,
|
||||
|
||||
@@ -32,7 +32,7 @@ openclaw onboard --auth-choice qianfan-api-key
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [OpenClaw Configuration](/configuration)
|
||||
- [OpenClaw Configuration](/gateway/configuration)
|
||||
- [Model Providers](/concepts/model-providers)
|
||||
- [Agent Setup](/agents)
|
||||
- [Agent Setup](/concepts/agent)
|
||||
- [Qianfan API Documentation](https://cloud.baidu.com/doc/qianfan-api/s/3m7of64lb)
|
||||
|
||||
@@ -211,4 +211,4 @@ Notes:
|
||||
- New connector templates depend only on SDK + runtime.
|
||||
- External plugins can be developed and updated without core source access.
|
||||
|
||||
Related docs: [Plugins](/plugin), [Channels](/channels/index), [Configuration](/gateway/configuration).
|
||||
Related docs: [Plugins](/tools/plugin), [Channels](/channels/index), [Configuration](/gateway/configuration).
|
||||
|
||||
@@ -29,7 +29,7 @@ OpenClaw features that can generate provider usage or paid API calls.
|
||||
- `openclaw status --usage` and `openclaw channels list` show provider **usage windows**
|
||||
(quota snapshots, not per-message costs).
|
||||
|
||||
See [Token use & costs](/token-use) for details and examples.
|
||||
See [Token use & costs](/reference/token-use) for details and examples.
|
||||
|
||||
## How keys are discovered
|
||||
|
||||
@@ -48,7 +48,7 @@ OpenClaw can pick up credentials from:
|
||||
Every reply or tool call uses the **current model provider** (OpenAI, Anthropic, etc). This is the
|
||||
primary source of usage and cost.
|
||||
|
||||
See [Models](/providers/models) for pricing config and [Token use & costs](/token-use) for display.
|
||||
See [Models](/providers/models) for pricing config and [Token use & costs](/reference/token-use) for display.
|
||||
|
||||
### 2) Media understanding (audio/image/video)
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
# 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.
|
||||
@@ -154,7 +154,7 @@ If you’re tuning limits:
|
||||
- The context window comes from the model catalog (and can be overridden via config).
|
||||
- `contextTokens` in the store is a runtime estimate/reporting value; don’t treat it as a strict guarantee.
|
||||
|
||||
For more, see [/token-use](/token-use).
|
||||
For more, see [/token-use](/reference/token-use).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ title: "Tests"
|
||||
|
||||
# Tests
|
||||
|
||||
- Full testing kit (suites, live, Docker): [Testing](/testing)
|
||||
- Full testing kit (suites, live, Docker): [Testing](/help/testing)
|
||||
|
||||
- `pnpm test:force`: Kills any lingering gateway process holding the default control port, then runs the full Vitest suite with an isolated gateway port so server tests don’t collide with a running instance. Use this when a prior gateway run left port 18789 occupied.
|
||||
- `pnpm test:coverage`: Runs Vitest with V8 coverage. Global thresholds are 70% lines/branches/functions/statements. Coverage excludes integration-heavy entrypoints (CLI wiring, gateway/telegram bridges, webchat static server) to keep the target focused on unit-testable logic.
|
||||
|
||||
@@ -19,7 +19,7 @@ For a complete map of the docs, see [Docs hubs](/start/hubs).
|
||||
- [Slash commands](/tools/slash-commands)
|
||||
- [Multi-agent routing](/concepts/multi-agent)
|
||||
- [Updating and rollback](/install/updating)
|
||||
- [Pairing (DM and nodes)](/start/pairing)
|
||||
- [Pairing (DM and nodes)](/channels/pairing)
|
||||
- [Nix mode](/install/nix)
|
||||
- [OpenClaw assistant setup](/start/openclaw)
|
||||
- [Skills](/tools/skills)
|
||||
@@ -41,8 +41,8 @@ For a complete map of the docs, see [Docs hubs](/start/hubs).
|
||||
- [Mattermost (plugin)](/channels/mattermost)
|
||||
- [BlueBubbles (iMessage)](/channels/bluebubbles)
|
||||
- [iMessage (legacy)](/channels/imessage)
|
||||
- [Groups](/concepts/groups)
|
||||
- [WhatsApp group messages](/concepts/group-messages)
|
||||
- [Groups](/channels/groups)
|
||||
- [WhatsApp group messages](/channels/group-messages)
|
||||
- [Media images](/nodes/images)
|
||||
- [Media audio](/nodes/audio)
|
||||
|
||||
|
||||
@@ -115,6 +115,6 @@ If the Control UI loads, your Gateway is ready for use.
|
||||
|
||||
## Next steps
|
||||
|
||||
- DM safety and approvals: [Pairing](/start/pairing)
|
||||
- DM safety and approvals: [Pairing](/channels/pairing)
|
||||
- Connect more channels: [Channels](/channels)
|
||||
- Advanced workflows and from source: [Setup](/start/setup)
|
||||
|
||||
@@ -61,9 +61,9 @@ Use these hubs to discover every page, including deep dives and reference docs t
|
||||
- [Presence](/concepts/presence)
|
||||
- [Discovery + transports](/gateway/discovery)
|
||||
- [Bonjour](/gateway/bonjour)
|
||||
- [Channel routing](/concepts/channel-routing)
|
||||
- [Groups](/concepts/groups)
|
||||
- [Group messages](/concepts/group-messages)
|
||||
- [Channel routing](/channels/channel-routing)
|
||||
- [Groups](/channels/groups)
|
||||
- [Group messages](/channels/group-messages)
|
||||
- [Model failover](/concepts/model-failover)
|
||||
- [OAuth](/concepts/oauth)
|
||||
|
||||
@@ -118,7 +118,7 @@ Use these hubs to discover every page, including deep dives and reference docs t
|
||||
- [Models](/concepts/models)
|
||||
- [Sub-agents](/tools/subagents)
|
||||
- [Agent send CLI](/tools/agent-send)
|
||||
- [Terminal UI](/tui)
|
||||
- [Terminal UI](/web/tui)
|
||||
- [Browser control](/tools/browser)
|
||||
- [Browser (Linux troubleshooting)](/tools/browser-linux-troubleshooting)
|
||||
- [Polls](/automation/poll)
|
||||
|
||||
@@ -166,7 +166,7 @@ Example (allow only file tools + browser):
|
||||
## Plugins + tools
|
||||
|
||||
Plugins can register **additional tools** (and CLI commands) beyond the core set.
|
||||
See [Plugins](/plugin) for install + config, and [Skills](/tools/skills) for how
|
||||
See [Plugins](/tools/plugin) for install + config, and [Skills](/tools/skills) for how
|
||||
tool usage guidance is injected into prompts. Some plugins ship their own skills
|
||||
alongside tools (for example, the voice-call plugin).
|
||||
|
||||
|
||||
@@ -331,7 +331,7 @@ OpenProse pairs well with Lobster: use `/prose` to orchestrate multi-agent prep,
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Plugins](/plugin)
|
||||
- [Plugins](/tools/plugin)
|
||||
- [Plugin tool authoring](/plugins/agent-tools)
|
||||
|
||||
## Case study: community workflows
|
||||
|
||||
@@ -44,7 +44,7 @@ Plugins can ship their own skills by listing `skills` directories in
|
||||
`openclaw.plugin.json` (paths relative to the plugin root). Plugin skills load
|
||||
when the plugin is enabled and participate in the normal skill precedence rules.
|
||||
You can gate them via `metadata.openclaw.requires.config` on the plugin’s config
|
||||
entry. See [Plugins](/plugin) for discovery/config and [Tools](/tools) for the
|
||||
entry. See [Plugins](/tools/plugin) for discovery/config and [Tools](/tools) for the
|
||||
tool surface those skills teach.
|
||||
|
||||
## ClawHub (install + sync)
|
||||
|
||||
@@ -9,7 +9,7 @@ x-i18n:
|
||||
model: claude-opus-4-5
|
||||
provider: pi
|
||||
source_hash: 853227a0f1abd20790b425fa64dda60efc6b5f93c1b13ecd2dcb788268f71d79
|
||||
source_path: hooks.md
|
||||
source_path: automation/hooks.md
|
||||
workflow: 15
|
||||
---
|
||||
|
||||
@@ -24,7 +24,7 @@ Hooks 是在事件发生时运行的小脚本。有两种类型:
|
||||
- **Hooks**(本页):当智能体事件触发时在 Gateway 网关内运行,如 `/new`、`/reset`、`/stop` 或生命周期事件。
|
||||
- **Webhooks**:外部 HTTP webhooks,让其他系统触发 OpenClaw 中的工作。参见 [Webhook Hooks](/automation/webhook) 或使用 `openclaw webhooks` 获取 Gmail 助手命令。
|
||||
|
||||
Hooks 也可以捆绑在插件中;参见 [插件](/plugin#plugin-hooks)。
|
||||
Hooks 也可以捆绑在插件中;参见 [插件](/tools/plugin#plugin-hooks)。
|
||||
|
||||
常见用途:
|
||||
|
||||
8
docs/zh-CN/automation/troubleshooting.md
Normal file
8
docs/zh-CN/automation/troubleshooting.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
summary: 自动化故障排查:排查 cron 和 heartbeat 调度与投递问题
|
||||
title: 自动化故障排查
|
||||
---
|
||||
|
||||
# 自动化故障排查
|
||||
|
||||
该页面是英文文档的中文占位版本,完整内容请先参考英文版:[Automation Troubleshooting](/automation/troubleshooting)。
|
||||
@@ -25,7 +25,7 @@ x-i18n:
|
||||
- OpenClaw 通过其 REST API 与之通信(`GET /api/v1/ping`、`POST /message/text`、`POST /chat/:id/*`)。
|
||||
- 传入消息通过 webhook 到达;发出的回复、输入指示器、已读回执和 tapback 均为 REST 调用。
|
||||
- 附件和贴纸作为入站媒体被接收(并在可能时呈现给智能体)。
|
||||
- 配对/白名单的工作方式与其他渠道相同(`/start/pairing` 等),使用 `channels.bluebubbles.allowFrom` + 配对码。
|
||||
- 配对/白名单的工作方式与其他渠道相同(`/channels/pairing` 等),使用 `channels.bluebubbles.allowFrom` + 配对码。
|
||||
- 回应作为系统事件呈现,与 Slack/Telegram 类似,智能体可以在回复前"提及"它们。
|
||||
- 高级功能:编辑、撤回、回复线程、消息效果、群组管理。
|
||||
|
||||
@@ -80,7 +80,7 @@ openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --passwor
|
||||
- 批准方式:
|
||||
- `openclaw pairing list bluebubbles`
|
||||
- `openclaw pairing approve bluebubbles <CODE>`
|
||||
- 配对是默认的令牌交换方式。详情:[配对](/start/pairing)
|
||||
- 配对是默认的令牌交换方式。详情:[配对](/channels/pairing)
|
||||
|
||||
群组:
|
||||
|
||||
@@ -268,4 +268,4 @@ OpenClaw 可能会显示*短*消息 ID(例如 `1`、`2`)以节省 token。
|
||||
- OpenClaw 会根据 BlueBubbles 服务器的 macOS 版本自动隐藏已知不可用的操作。如果在 macOS 26(Tahoe)上编辑仍然显示,请使用 `channels.bluebubbles.actions.edit=false` 手动禁用。
|
||||
- 查看状态/健康信息:`openclaw status --all` 或 `openclaw status --deep`。
|
||||
|
||||
有关通用渠道工作流参考,请参阅[渠道](/channels)和[插件](/plugins)指南。
|
||||
有关通用渠道工作流参考,请参阅[渠道](/channels)和[插件](/tools/plugin)指南。
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user