mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-27 18:01:53 +08:00
Compare commits
1 Commits
codex/i18n
...
fix/gatewa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40a422837b |
@@ -146,7 +146,7 @@ Default guidance:
|
||||
|
||||
Default Completeness bands:
|
||||
|
||||
- `Clawesome` (95-100): complete across expected workflows, variants, and
|
||||
- `Lovable` (95-100): complete across expected workflows, variants, and
|
||||
recovery branches, with only minor polish gaps.
|
||||
- `Stable` (80-95): the expected workflow set is broadly present, with only
|
||||
bounded missing branches.
|
||||
@@ -172,7 +172,7 @@ Default Completeness bands:
|
||||
|
||||
Bands:
|
||||
|
||||
- `Clawesome`: 95-100
|
||||
- `Lovable`: 95-100
|
||||
- `Stable`: 80-95
|
||||
- `Beta`: 70-80
|
||||
- `Alpha`: 50-70
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
---
|
||||
name: openclaw-ci-limits
|
||||
description: Manage OpenClaw GitHub Actions and Blacksmith CI capacity, runner-registration budgets, fanout caps, main-push debounce, shard sizing, hosted-runner offload, queue health, and safe ramp-down/ramp-up changes. Use when tuning `.github/workflows/*`, `docs/ci.md`, CI runner labels, matrix `max-parallel`, ClawSweeper/Blacksmith burst protection, CodeQL runner placement, or investigating slow/queued OpenClaw CI.
|
||||
---
|
||||
|
||||
# OpenClaw CI Limits
|
||||
|
||||
Use this skill for CI capacity changes, not ordinary test failure triage. The
|
||||
goal is to keep OpenClaw fast while staying below GitHub's self-hosted runner
|
||||
registration edge limit.
|
||||
|
||||
## Core Facts
|
||||
|
||||
- The scarce resource is Blacksmith runner registrations, not Blacksmith vCPU
|
||||
capacity.
|
||||
- GitHub runner registrations for `openclaw` are currently capped at 3,000 per
|
||||
5 minutes per repository, organization, or enterprise. The `openclaw`
|
||||
organization shares one bucket.
|
||||
- Core REST quota does not draw down this bucket. Check
|
||||
`actions_runner_registration` separately; core quota can be healthy while
|
||||
runner registration is throttled.
|
||||
- Use 2,000 registrations per 5 minutes as the operating target. Leave the last
|
||||
third for other repos, retries, and burst overlap.
|
||||
- Jobs that route, notify, summarize, choose shards, or run short CodeQL quality
|
||||
scans should stay on GitHub-hosted runners unless measured evidence says
|
||||
Blacksmith is required.
|
||||
|
||||
## First Checks
|
||||
|
||||
Before changing CI, collect current pressure:
|
||||
|
||||
```bash
|
||||
ghx api rate_limit --jq '{core:.resources.core,graphql:.resources.graphql,search:.resources.search,actions_runner_registration:.resources.actions_runner_registration}'
|
||||
ghx run list -R openclaw/openclaw --limit 20 --json databaseId,status,conclusion,workflowName,event,headBranch,createdAt,updatedAt,url
|
||||
ghx run list -R openclaw/clawsweeper --limit 20 --json databaseId,status,conclusion,workflowName,event,headBranch,createdAt,updatedAt,url
|
||||
curl -fsS https://clawsweeper.openclaw.ai/api/status | jq '{generated_at,fleet,diagnostics:{errors:.diagnostics.errors}}'
|
||||
curl -fsS https://clawsweeper.openclaw.ai/api/exact-review-queue | jq '.'
|
||||
node scripts/ci-run-timings.mjs --latest-main
|
||||
node scripts/ci-run-timings.mjs --recent 10
|
||||
```
|
||||
|
||||
Read:
|
||||
|
||||
- `.github/workflows/ci.yml`
|
||||
- `.github/workflows/codeql-critical-quality.yml`
|
||||
- `docs/ci.md`
|
||||
- `test/scripts/ci-workflow-guards.test.ts`
|
||||
- touched planner files under `scripts/lib/*ci*`, `scripts/lib/*test-plan*`, or
|
||||
`scripts/ci-changed-scope.mjs`
|
||||
|
||||
## Diagnose The Bottleneck
|
||||
|
||||
Classify the issue before changing caps:
|
||||
|
||||
- **Runner-registration throttle:** many jobs queued before runner assignment,
|
||||
Blacksmith/GitHub reports 403/429 or spam-style 422 responses from
|
||||
`generate-jitconfig`, and API core quota is still healthy. Treat 422 as this
|
||||
signal only when the request payload is otherwise valid. Fix burstiness and
|
||||
Blacksmith job count.
|
||||
- **Blacksmith capacity:** Blacksmith dashboard shows actual concurrency caps or
|
||||
unavailable capacity. Do not solve this with GitHub workflow fanout alone.
|
||||
- **OpenClaw test runtime:** jobs start quickly but one lane dominates wall time.
|
||||
Use `$openclaw-test-performance` instead of runner tuning.
|
||||
- **Real failing CI:** one job fails after starting. Use `$github:gh-fix-ci` or
|
||||
`$openclaw-testing`, not this skill.
|
||||
- **ClawSweeper backlog:** exact-review queue grows while CI is healthy. Tune
|
||||
ClawSweeper workers in `openclaw/clawsweeper`, not OpenClaw CI.
|
||||
|
||||
## Registration Budget Math
|
||||
|
||||
Estimate worst-case registrations for a change before editing:
|
||||
|
||||
```text
|
||||
new Blacksmith registrations ~= number of Blacksmith jobs that can become queued
|
||||
inside one 5 minute window
|
||||
```
|
||||
|
||||
For matrix jobs, count every row that can start in the 5-minute window.
|
||||
`strategy.max-parallel` only caps simultaneous rows; short rows can turn over
|
||||
and register more runners before the window resets. Use job duration, retries,
|
||||
and queue turnover to justify any lower estimate. Add non-matrix Blacksmith jobs
|
||||
such as `preflight`, `security-fast`, `build-artifacts`, and platform lanes.
|
||||
|
||||
For repeated pushes, multiply by the number of runs expected to reach
|
||||
Blacksmith admission in the same 5-minute window, including runs canceled after
|
||||
admission. The debounce only suppresses pushes that arrive while
|
||||
`runner-admission` is still sleeping; once Blacksmith jobs register, those
|
||||
registrations are spent even if a later push cancels the run. If timing is
|
||||
uncertain, count every sequential push in the window.
|
||||
|
||||
Reject a change unless the org-level worst case stays below 2,000 registrations
|
||||
per 5 minutes with headroom for ClawSweeper, ClawHub, Clownfish, OpenClaw RTT,
|
||||
and Clawbench.
|
||||
|
||||
## Safe Levers
|
||||
|
||||
Prefer these in order:
|
||||
|
||||
1. Add or preserve concurrency groups that cancel superseded PR and canonical
|
||||
`main` runs before Blacksmith work starts.
|
||||
2. Keep the `runner-admission` hosted debounce for canonical `main` pushes.
|
||||
Change `OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS` only with evidence.
|
||||
3. Move high-frequency, short, non-build jobs to `ubuntu-24.04`.
|
||||
4. Reduce matrix rows by bundling related tests inside one runner job when the
|
||||
combined job stays under timeout and keeps useful failure names.
|
||||
5. Lower `strategy.max-parallel` for bursty Blacksmith matrices.
|
||||
6. Right-size runners from timing evidence. Use fewer/larger jobs only when
|
||||
elapsed time improves enough to justify registration count.
|
||||
7. Split truly slow tests with `$openclaw-test-performance`; do not hide a slow
|
||||
test problem by registering more runners.
|
||||
|
||||
Do not:
|
||||
|
||||
- add another Blacksmith installation expecting a higher registration bucket;
|
||||
- move CodeQL Critical Quality back to Blacksmith;
|
||||
- raise all `max-parallel` values at once;
|
||||
- make manual `workflow_dispatch` runs cancel normal push/PR validation;
|
||||
- delete coverage just to reduce runner count;
|
||||
- treat cancelled superseded runs as failures without checking the newest run
|
||||
for the same ref.
|
||||
|
||||
## Current OpenClaw Knobs
|
||||
|
||||
These are intentionally guarded by `test/scripts/ci-workflow-guards.test.ts`:
|
||||
|
||||
- `CI` concurrency key version and `cancel-in-progress` for PRs and canonical
|
||||
`main` pushes.
|
||||
- `runner-admission` on `ubuntu-24.04` with
|
||||
`OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS=90`.
|
||||
- `preflight` and `security-fast` needing `runner-admission`.
|
||||
- CI matrix caps: fast/check lanes at 12, Node test shards at 24, Windows and
|
||||
Android at 2.
|
||||
- `build-artifacts` on `blacksmith-16vcpu-ubuntu-2404`.
|
||||
- lower-weight Node/check shards on `blacksmith-4vcpu-ubuntu-2404`.
|
||||
- heavy retained Linux/Android shards on `blacksmith-8vcpu-ubuntu-2404`.
|
||||
- CodeQL Critical Quality on `ubuntu-24.04` with no `blacksmith-` labels.
|
||||
|
||||
When changing one knob, update `docs/ci.md` and the guard test in the same PR.
|
||||
|
||||
## Validation
|
||||
|
||||
For workflow-only or docs/skill-only changes in a Codex worktree:
|
||||
|
||||
```bash
|
||||
node scripts/run-vitest.mjs test/scripts/ci-workflow-guards.test.ts
|
||||
node scripts/check-workflows.mjs
|
||||
node scripts/docs-list.js
|
||||
./node_modules/.bin/oxfmt --check .github/workflows/ci.yml .github/workflows/codeql-critical-quality.yml docs/ci.md test/scripts/ci-workflow-guards.test.ts .agents/skills/openclaw-ci-limits/SKILL.md .agents/skills/openclaw-ci-limits/agents/openai.yaml
|
||||
git diff --check
|
||||
```
|
||||
|
||||
If `pnpm docs:list` tries to reconcile dependencies in a linked Codex worktree,
|
||||
stop and use `node scripts/docs-list.js`.
|
||||
|
||||
For a PR before requesting maintainer approval:
|
||||
|
||||
```bash
|
||||
.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
|
||||
ghx pr checks <pr> -R openclaw/openclaw --watch --interval 15
|
||||
```
|
||||
|
||||
Use hosted exact-head gates for CI workflow tuning. Do not burn local
|
||||
`pnpm test` on unrelated full-suite proof.
|
||||
|
||||
Only after the maintainer explicitly asks you to prepare or land the PR, run the
|
||||
repo-native mutating wrapper:
|
||||
|
||||
```bash
|
||||
scripts/pr review-init <pr>
|
||||
scripts/pr review-artifacts-init <pr>
|
||||
scripts/pr review-validate-artifacts <pr>
|
||||
OPENCLAW_TESTBOX=1 scripts/pr prepare-run <pr>
|
||||
```
|
||||
|
||||
`prepare-run` can push a prepared commit to the PR branch. Only run
|
||||
`scripts/pr merge-run <pr>` after the maintainer has explicitly asked you to
|
||||
land the PR. Both commands mutate GitHub state.
|
||||
|
||||
## Post-Land Monitoring
|
||||
|
||||
After merge, watch at least one fresh main cycle and the adjacent repos:
|
||||
|
||||
```bash
|
||||
ghx run list -R openclaw/openclaw --limit 20 --json databaseId,status,conclusion,workflowName,event,headBranch,createdAt,updatedAt,url
|
||||
for repo in openclaw/clawsweeper openclaw/clawhub openclaw/clownfish openclaw/openclaw-rtt openclaw/clawbench; do
|
||||
ghx run list -R "$repo" --limit 12 --json databaseId,status,conclusion,workflowName,event,headBranch,createdAt,updatedAt,url
|
||||
done
|
||||
curl -fsS https://clawsweeper.openclaw.ai/api/exact-review-queue | jq '.'
|
||||
```
|
||||
|
||||
Report:
|
||||
|
||||
- exact PR/commit landed;
|
||||
- expected registration reduction or added headroom;
|
||||
- CI run status and slowest/queued jobs;
|
||||
- ClawSweeper queue pending, dispatching, leased, oldest pending age;
|
||||
- any real failures that remain outside runner registration.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "OpenClaw CI Limits"
|
||||
short_description: "Tune OpenClaw CI fanout and runner budgets"
|
||||
default_prompt: "Use $openclaw-ci-limits to inspect OpenClaw CI pressure, tune runner-registration fanout safely, and document the exact validation before landing."
|
||||
@@ -5,7 +5,7 @@ description: "Run or recover OpenClaw macOS release signing, notarization, appca
|
||||
|
||||
# OpenClaw Mac Release
|
||||
|
||||
Use with `$release-openclaw-maintainer`, `$release-openclaw-ci`, `$one-password`, and `$release-private` if it exists when stable macOS assets, release-ops mac preflight, notarization, appcast promotion, or mac release recovery is involved.
|
||||
Use with `$release-openclaw-maintainer`, `$release-openclaw-ci`, `$one-password`, and `$release-private` if it exists when stable macOS assets, private mac preflight, notarization, appcast promotion, or mac release recovery is involved.
|
||||
|
||||
## Credentials
|
||||
|
||||
@@ -23,7 +23,7 @@ Use with `$release-openclaw-maintainer`, `$release-openclaw-ci`, `$one-password`
|
||||
|
||||
## GitHub Secrets
|
||||
|
||||
Target release-ops repo environment: `openclaw/releases`, env `mac-release`.
|
||||
Target private repo environment: `openclaw/releases-private`, env `mac-release`.
|
||||
|
||||
Set only after local notary auth validation:
|
||||
|
||||
@@ -35,24 +35,12 @@ Do not update these from mixed sources. All three ASC fields must come from the
|
||||
|
||||
## Workflow Shape
|
||||
|
||||
- `openclaw/openclaw` is the public product repo. Its GitHub Releases page is
|
||||
where macOS assets are ultimately attached.
|
||||
- `openclaw/openclaw` `macos-release.yml` is public handoff validation only.
|
||||
It never signs, notarizes, or uploads macOS assets, regardless of
|
||||
`preflight_only`.
|
||||
- `openclaw/releases` is the restricted release-ops repo. Its macOS workflows
|
||||
sign, notarize, validate, and promote assets onto the
|
||||
`openclaw/openclaw` GitHub release.
|
||||
- Public release branch may carry mac-only packaging fixes after the stable tag/npm are already live.
|
||||
- Use `source_ref=release/YYYY.M.PATCH` for release-ops mac preflight/validation when building that branch variation.
|
||||
- Use `source_ref=release/YYYY.M.PATCH` for private mac preflight/validation when building that branch variation.
|
||||
- Keep `tag=vYYYY.M.PATCH` pointing at the original stable release commit.
|
||||
- Real mac publish must reuse:
|
||||
- a successful release-ops mac preflight run for the same tag/source SHA
|
||||
- a successful release-ops mac validation run for the same tag/source SHA
|
||||
- Release-ops preflight and real publish enter the protected `mac-release`
|
||||
environment in the `build_sign_and_package` job. Operators may be able to
|
||||
trigger the workflow while Vincent or another environment reviewer approves
|
||||
the paused deployment before signing/notarization/promotion proceeds.
|
||||
- a successful private mac preflight run for the same tag/source SHA
|
||||
- a successful private mac validation run for the same tag/source SHA
|
||||
- If preflight source SHA differs from tag SHA, validation must also use the same `source_ref`; promotion rejects mismatched proof.
|
||||
|
||||
## Notarization
|
||||
@@ -64,25 +52,10 @@ Do not update these from mixed sources. All three ASC fields must come from the
|
||||
|
||||
## Dispatch
|
||||
|
||||
Public handoff validation:
|
||||
Private preflight:
|
||||
|
||||
```bash
|
||||
gh workflow run macos-release.yml --repo openclaw/openclaw \
|
||||
--ref release/YYYY.M.PATCH \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f preflight_only=true \
|
||||
-f public_release_branch=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
- Use the public release branch as the workflow ref so the Actions list displays
|
||||
`release/YYYY.M.PATCH`, matching prior stable macOS handoff runs.
|
||||
- Do not use `--ref main` or `--ref vYYYY.M.PATCH` for this public handoff
|
||||
validation. The workflow checks out the tag from the `tag` input internally.
|
||||
|
||||
Release-ops preflight:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases --ref main \
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --ref main \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f source_ref=release/YYYY.M.PATCH \
|
||||
-f preflight_only=true \
|
||||
@@ -91,24 +64,18 @@ gh workflow run openclaw-macos-publish.yml --repo openclaw/releases --ref main \
|
||||
-f public_release_branch=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
Wait for the run to reach the `mac-release` environment approval if GitHub
|
||||
pauses it, then get approval from Vincent or another configured environment
|
||||
reviewer. Record the successful preflight run id.
|
||||
|
||||
Release-ops validation for a branch-variation preflight:
|
||||
Private validation for a branch-variation preflight:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-macos-validate.yml --repo openclaw/releases --ref main \
|
||||
gh workflow run openclaw-macos-validate.yml --repo openclaw/releases-private --ref main \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f source_ref=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
Record the successful validation run id.
|
||||
|
||||
Real publish:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases --ref main \
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --ref main \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f preflight_only=false \
|
||||
-f smoke_test_only=false \
|
||||
@@ -118,14 +85,6 @@ gh workflow run openclaw-macos-publish.yml --repo openclaw/releases --ref main \
|
||||
-f public_release_branch=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
Wait for the `mac-release` environment approval again if GitHub pauses the real
|
||||
publish run before it promotes assets.
|
||||
|
||||
- Release-ops `openclaw/releases` publish/validate workflows run from their own
|
||||
trusted `main` workflow ref. Real publish has a guard that rejects any other
|
||||
workflow ref. That displayed `main` ref is expected; the public OpenClaw
|
||||
source is selected by `tag` and optional `source_ref`.
|
||||
|
||||
## Verify
|
||||
|
||||
- `gh release view vYYYY.M.PATCH --repo openclaw/openclaw` shows zip, dmg, dSYM zip, not draft, not prerelease.
|
||||
|
||||
@@ -203,9 +203,8 @@ Stable publication is not complete until `main` carries the actual shipped relea
|
||||
validation-only release machinery. If mac packaging needs release-branch-only
|
||||
fixes after the stable npm package or GitHub tag is already published, do not
|
||||
create a `vYYYY.M.PATCH-N` correction tag just to change the workflow source.
|
||||
Dispatch the release-ops mac workflows for the original `tag=vYYYY.M.PATCH`
|
||||
with `source_ref=release/YYYY.M.PATCH` and
|
||||
`public_release_branch=release/YYYY.M.PATCH`;
|
||||
Dispatch the private mac workflows for the original `tag=vYYYY.M.PATCH` with
|
||||
`source_ref=release/YYYY.M.PATCH` and `public_release_branch=release/YYYY.M.PATCH`;
|
||||
provenance checks must prove the source SHA descends from the tag and
|
||||
validation/preflight use the same source. Reserve `vYYYY.M.PATCH-N` correction
|
||||
tags for emergency hotfixes that must publish a new npm package/release
|
||||
@@ -580,8 +579,8 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Actual npm install/update phases are capped at 5 minutes. If `npm install -g`, installer package install, or `openclaw update` takes longer than 300s in release e2e, stop treating the run as healthy progress and debug the installer/updater or harness.
|
||||
- Serialize host build/package mutations ahead of VM lanes. Finish `pnpm build`, `pnpm ui:build`, `pnpm release:check`, install smoke, and any Docker/package-prep lanes before starting Parallels `npm pack` lanes; otherwise `dist` can disappear during VM pack prep and produce false failures.
|
||||
- Include mac release readiness in preflight by running the public validation
|
||||
workflow in `openclaw/openclaw` and the release-ops mac preflight in
|
||||
`openclaw/releases` for every release.
|
||||
workflow in `openclaw/openclaw` and the real mac preflight in
|
||||
`openclaw/releases-private` for every release.
|
||||
- Treat the `appcast.xml` update on `main` as part of mac release readiness, not an optional follow-up.
|
||||
- The workflows remain tag-based. The agent is responsible for making sure
|
||||
preflight runs complete successfully before any publish run starts.
|
||||
@@ -609,16 +608,16 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
## Use the right auth flow
|
||||
|
||||
- OpenClaw publish uses GitHub trusted publishing.
|
||||
- Stable npm promotion from `beta` to `latest` uses the restricted release-ops
|
||||
`openclaw/releases/.github/workflows/openclaw-npm-dist-tags.yml` workflow
|
||||
because `npm dist-tag` management needs `NPM_TOKEN`, while the public npm
|
||||
release workflow stays OIDC-only.
|
||||
- Prefer fixing the release-ops workflow token path over any local 1Password
|
||||
fallback. The desired setup is a granular npm token stored as the release-ops
|
||||
- Stable npm promotion from `beta` to `latest` uses the private
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow because `npm dist-tag` management needs `NPM_TOKEN`, while the
|
||||
public npm release workflow stays OIDC-only.
|
||||
- Prefer fixing the private workflow token path over any local 1Password
|
||||
fallback. The desired setup is a granular npm token stored as the private
|
||||
repo's `NPM_TOKEN` secret, scoped to the `openclaw` package with read/write
|
||||
and 2FA bypass for automation.
|
||||
- If the release-ops dist-tag workflow cannot promote because `NPM_TOKEN` is
|
||||
absent or stale, use the local tmux + 1Password fallback:
|
||||
- If the private dist-tag workflow cannot promote because `NPM_TOKEN` is absent
|
||||
or stale, use the local tmux + 1Password fallback:
|
||||
- Start or reuse a tmux session so interactive `npm login` and OTP prompts
|
||||
are observable and recoverable.
|
||||
- Hard rule: never run `op` directly in the main agent shell during release
|
||||
@@ -636,21 +635,21 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Verify with a cache-bypassed registry read, for example:
|
||||
`npm view openclaw dist-tags --json --prefer-online --cache /tmp/openclaw-npm-cache-verify-$$`
|
||||
and `npm view openclaw@latest version dist.tarball --json --prefer-online`.
|
||||
- Direct stable publishes can also use that release-ops dist-tag workflow to
|
||||
point `beta` at the already-published `latest` version when the operator wants
|
||||
both tags aligned immediately.
|
||||
- Direct stable publishes can also use that private dist-tag workflow to point
|
||||
`beta` at the already-published `latest` version when the operator wants both
|
||||
tags aligned immediately.
|
||||
- The publish run must be started manually with `workflow_dispatch`.
|
||||
- The npm workflow and the release-ops mac publish workflow accept
|
||||
- The npm workflow and the private mac publish workflow accept
|
||||
`preflight_only=true` to run validation/build/package steps without uploading
|
||||
public release assets.
|
||||
- Real npm publish requires a prior successful npm preflight run id and the
|
||||
successful Full Release Validation run id for the same tag/SHA so the publish
|
||||
job promotes the prepared tarball instead of rebuilding it and attaches the
|
||||
correct release evidence.
|
||||
- Real release-ops mac publish requires a prior successful release-ops mac
|
||||
preflight run id so the publish job promotes the prepared artifacts instead of
|
||||
- Real private mac publish requires a prior successful private mac preflight
|
||||
run id so the publish job promotes the prepared artifacts instead of
|
||||
rebuilding or renotarizing them again.
|
||||
- The release-ops mac workflow also accepts `smoke_test_only=true` for branch-safe
|
||||
- The private mac workflow also accepts `smoke_test_only=true` for branch-safe
|
||||
workflow smoke tests that use ad-hoc signing, skip notarization, skip shared
|
||||
appcast generation, and do not prove release readiness.
|
||||
- `preflight_only=true` on the npm workflow is also the right way to validate an
|
||||
@@ -671,27 +670,27 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
use only `main` or `release/YYYY.M.PATCH`.
|
||||
- `.github/workflows/macos-release.yml` in `openclaw/openclaw` is now a
|
||||
public validation-only handoff. It validates the tag/release state and points
|
||||
operators to the release-ops repo. It still rebuilds the JS outputs needed for
|
||||
operators to the private repo. It still rebuilds the JS outputs needed for
|
||||
release validation, but it does not sign, notarize, or publish macOS
|
||||
artifacts.
|
||||
- `openclaw/releases/.github/workflows/openclaw-macos-validate.yml` is the
|
||||
required release-ops mac validation lane for `swift test`; keep it green
|
||||
- `openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
|
||||
is the required private mac validation lane for `swift test`; keep it green
|
||||
before any real stable mac publish run starts.
|
||||
- Real mac preflight and real mac publish both use
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml`.
|
||||
- The release-ops mac validation lane runs on GitHub's standard macOS runner.
|
||||
- The release-ops mac preflight path runs on GitHub's xlarge macOS runner and uses
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`.
|
||||
- The private mac validation lane runs on GitHub's standard macOS runner.
|
||||
- The private mac preflight path runs on GitHub's xlarge macOS runner and uses
|
||||
a SwiftPM cache because the build/sign/notarize/package path is CPU-heavy.
|
||||
- Release-ops mac preflight uploads notarized build artifacts as workflow
|
||||
artifacts instead of uploading public GitHub release assets.
|
||||
- Release-ops smoke-test runs upload ad-hoc, non-notarized build artifacts as
|
||||
- Private mac preflight uploads notarized build artifacts as workflow artifacts
|
||||
instead of uploading public GitHub release assets.
|
||||
- Private smoke-test runs upload ad-hoc, non-notarized build artifacts as
|
||||
workflow artifacts and intentionally skip stable `appcast.xml` generation.
|
||||
- For stable releases, npm preflight, Full Release Validation, public mac
|
||||
validation, release-ops mac validation, and release-ops mac preflight must all
|
||||
pass before any real publish run starts. For beta releases, npm preflight and
|
||||
Full Release Validation must pass before npm publish unless the operator
|
||||
explicitly waives the full gate; mac beta validation is still only required
|
||||
when requested.
|
||||
validation, private mac validation, and private mac preflight must all pass
|
||||
before any real publish run starts. For beta releases, npm preflight and Full
|
||||
Release Validation must pass before npm publish unless the operator explicitly
|
||||
waives the full gate; mac beta validation is still only required when
|
||||
requested.
|
||||
- Real publish runs may be dispatched from `main` or from a
|
||||
`release/YYYY.M.PATCH` branch. For release-branch runs, the tag must be contained
|
||||
in that release branch, and the real publish must reuse a successful preflight
|
||||
@@ -700,21 +699,21 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
rather than workflow-level SHA pinning.
|
||||
- The `npm-release` environment must be approved by `@openclaw/openclaw-release-managers` before publish continues.
|
||||
- Mac publish uses
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml` for
|
||||
release-ops mac preflight artifact preparation and real publish artifact
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml` for
|
||||
private mac preflight artifact preparation and real publish artifact
|
||||
promotion.
|
||||
- Real release-ops mac publish uploads the packaged `.zip`, `.dmg`, and
|
||||
- Real private mac publish uploads the packaged `.zip`, `.dmg`, and
|
||||
`.dSYM.zip` assets to the existing GitHub release in `openclaw/openclaw`
|
||||
automatically when `OPENCLAW_PUBLIC_REPO_RELEASE_TOKEN` is present in the
|
||||
release-ops repo `mac-release` environment.
|
||||
private repo `mac-release` environment.
|
||||
- For stable releases, the agent must also download the signed
|
||||
`macos-appcast-<tag>` artifact from the successful release-ops mac workflow
|
||||
and then update `appcast.xml` on `main`.
|
||||
`macos-appcast-<tag>` artifact from the successful private mac workflow and
|
||||
then update `appcast.xml` on `main`.
|
||||
- For beta mac releases, do not update the shared production `appcast.xml`
|
||||
unless a separate beta Sparkle feed exists.
|
||||
- The release-ops repo targets a dedicated `mac-release` environment. If the
|
||||
GitHub plan does not yet support required reviewers there, do not assume the
|
||||
environment alone is the approval boundary; rely on restricted repo access and
|
||||
- The private repo targets a dedicated `mac-release` environment. If the GitHub
|
||||
plan does not yet support required reviewers there, do not assume the
|
||||
environment alone is the approval boundary; rely on private repo access and
|
||||
CODEOWNERS until those settings can be enabled.
|
||||
- Do not use `NPM_TOKEN` or the plugin OTP flow for the OpenClaw package
|
||||
publish path; package publishing uses trusted publishing.
|
||||
@@ -801,12 +800,12 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
18. For stable releases, start `.github/workflows/macos-release.yml` in
|
||||
`openclaw/openclaw` and wait for the public validation-only run to pass.
|
||||
19. For stable releases, start
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-validate.yml` with the
|
||||
same tag and wait for the release-ops mac validation lane to pass.
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
|
||||
with the same tag and wait for the private mac validation lane to pass.
|
||||
20. For stable releases, start
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml` with
|
||||
`preflight_only=true` and wait for it to pass. Save that run id because the
|
||||
real publish requires it to reuse the notarized mac artifacts.
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
with `preflight_only=true` and wait for it to pass. Save that run id because
|
||||
the real publish requires it to reuse the notarized mac artifacts.
|
||||
21. If any preflight or validation run fails, fix the issue on a new commit,
|
||||
delete the tag and any accidental draft/incomplete GitHub release, recreate
|
||||
the tag from the fixed commit, and rerun all relevant preflights from
|
||||
@@ -862,23 +861,22 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
promotion roster when the matching beta already carried the full confidence
|
||||
pass: published npm postpublish verify, Docker install/update smoke,
|
||||
macOS-only Parallels install/update smoke, and required QA signal.
|
||||
Then start the restricted release-ops
|
||||
`openclaw/releases/.github/workflows/openclaw-npm-dist-tags.yml` workflow
|
||||
to promote that stable version from `beta` to `latest`, then verify
|
||||
`latest` now points at that version.
|
||||
Then start the private
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow to promote that stable version from `beta` to `latest`, then
|
||||
verify `latest` now points at that version.
|
||||
29. If the stable release was published directly to `latest` and `beta` should
|
||||
follow it, start that same release-ops dist-tag workflow to point `beta` at
|
||||
the stable version, then verify both `latest` and `beta` point at that
|
||||
version.
|
||||
follow it, start that same private dist-tag workflow to point `beta` at the
|
||||
stable version, then verify both `latest` and `beta` point at that version.
|
||||
30. For stable releases, start
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml` for the
|
||||
real publish with the successful release-ops mac `preflight_run_id` and wait
|
||||
for success.
|
||||
31. Verify the successful real release-ops mac run uploaded the `.zip`, `.dmg`,
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
for the real publish with the successful private mac `preflight_run_id` and
|
||||
wait for success.
|
||||
31. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
|
||||
and `.dSYM.zip` artifacts to the existing GitHub release in
|
||||
`openclaw/openclaw`.
|
||||
32. For stable releases, download `macos-appcast-<tag>` from the successful
|
||||
release-ops mac run, update `appcast.xml` on `main`, verify the feed, then
|
||||
private mac run, update `appcast.xml` on `main`, verify the feed, then
|
||||
complete the **Close stable releases on main** gate.
|
||||
33. For beta releases, publish the mac assets only when intentionally requested;
|
||||
expect no shared production
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# OpenClaw Maturity Scorecard Agent
|
||||
|
||||
You are refreshing the OpenClaw maturity score source for a release scorecard.
|
||||
|
||||
Goal: use the `$claw-score` skill to refresh `qa/maturity-scores.yaml` for every active surface in `taxonomy.yaml`, using the current repository and the release evidence artifacts in `.artifacts/maturity-evidence`.
|
||||
|
||||
Allowed tracked paths:
|
||||
|
||||
- `qa/maturity-scores.yaml`
|
||||
|
||||
Hard limits:
|
||||
|
||||
- Do not edit generated docs, taxonomy, workflows, scripts, package metadata, lockfiles, tests, or application code.
|
||||
- Do not render docs. The workflow renders docs after validating the score source.
|
||||
- Keep the score source schema valid for QA Lab maturity score validation.
|
||||
|
||||
Required workflow:
|
||||
|
||||
1. Use the `$claw-score` skill before editing.
|
||||
2. Read `taxonomy.yaml`, any existing maturity score file, and the release evidence artifacts.
|
||||
3. Refresh scores for every active surface in `taxonomy.yaml`.
|
||||
4. Run the QA Lab maturity score validation used by this repository.
|
||||
5. If no defensible score update is possible, leave a valid `qa/maturity-scores.yaml` and explain the uncertainty in the final message.
|
||||
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@@ -118,7 +118,6 @@
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/qa-lab/**"
|
||||
- "qa/scenarios/**"
|
||||
- "docs/maturity/**"
|
||||
- "docs/concepts/qa-e2e-automation.md"
|
||||
- "docs/concepts/personal-agent-benchmark-pack.md"
|
||||
- "docs/channels/qa-channel.md"
|
||||
|
||||
17
.github/workflows/ci-build-artifacts-testbox.yml
vendored
17
.github/workflows/ci-build-artifacts-testbox.yml
vendored
@@ -198,19 +198,10 @@ jobs:
|
||||
"+refs/heads/main:refs/remotes/origin/main"
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
link_node_tool() {
|
||||
local tool="$1"
|
||||
local source="$node_bin/$tool"
|
||||
local target="/usr/local/bin/$tool"
|
||||
if [ -e "$target" ] && [ "$(readlink -f "$source")" = "$(readlink -f "$target")" ]; then
|
||||
return
|
||||
fi
|
||||
sudo ln -sf "$source" "$target"
|
||||
}
|
||||
link_node_tool node
|
||||
link_node_tool npm
|
||||
link_node_tool npx
|
||||
link_node_tool corepack
|
||||
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
||||
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
||||
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
|
||||
#!/usr/bin/env bash
|
||||
exec /usr/local/bin/corepack pnpm "$@"
|
||||
|
||||
17
.github/workflows/ci-check-arm-testbox.yml
vendored
17
.github/workflows/ci-check-arm-testbox.yml
vendored
@@ -116,19 +116,10 @@ jobs:
|
||||
"+refs/heads/main:refs/remotes/origin/main"
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
link_node_tool() {
|
||||
local tool="$1"
|
||||
local source="$node_bin/$tool"
|
||||
local target="/usr/local/bin/$tool"
|
||||
if [ -e "$target" ] && [ "$(readlink -f "$source")" = "$(readlink -f "$target")" ]; then
|
||||
return
|
||||
fi
|
||||
sudo ln -sf "$source" "$target"
|
||||
}
|
||||
link_node_tool node
|
||||
link_node_tool npm
|
||||
link_node_tool npx
|
||||
link_node_tool corepack
|
||||
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
||||
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
||||
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
|
||||
#!/usr/bin/env bash
|
||||
exec /usr/local/bin/corepack pnpm "$@"
|
||||
|
||||
17
.github/workflows/ci-check-testbox.yml
vendored
17
.github/workflows/ci-check-testbox.yml
vendored
@@ -105,19 +105,10 @@ jobs:
|
||||
"+refs/heads/main:refs/remotes/origin/main"
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
link_node_tool() {
|
||||
local tool="$1"
|
||||
local source="$node_bin/$tool"
|
||||
local target="/usr/local/bin/$tool"
|
||||
if [ -e "$target" ] && [ "$(readlink -f "$source")" = "$(readlink -f "$target")" ]; then
|
||||
return
|
||||
fi
|
||||
sudo ln -sf "$source" "$target"
|
||||
}
|
||||
link_node_tool node
|
||||
link_node_tool npm
|
||||
link_node_tool npx
|
||||
link_node_tool corepack
|
||||
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
||||
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
||||
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
|
||||
#!/usr/bin/env bash
|
||||
exec /usr/local/bin/corepack pnpm "$@"
|
||||
|
||||
150
.github/workflows/ci.yml
vendored
150
.github/workflows/ci.yml
vendored
@@ -100,7 +100,6 @@ jobs:
|
||||
run_macos_node: ${{ steps.manifest.outputs.run_macos_node }}
|
||||
macos_node_matrix: ${{ steps.manifest.outputs.macos_node_matrix }}
|
||||
run_macos_swift: ${{ steps.manifest.outputs.run_macos_swift }}
|
||||
run_ios_build: ${{ steps.manifest.outputs.run_ios_build }}
|
||||
run_android_job: ${{ steps.manifest.outputs.run_android_job }}
|
||||
android_matrix: ${{ steps.manifest.outputs.android_matrix }}
|
||||
steps:
|
||||
@@ -205,7 +204,6 @@ jobs:
|
||||
OPENCLAW_CI_DOCS_CHANGED: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.docs_scope.outputs.docs_changed }}
|
||||
OPENCLAW_CI_RUN_NODE: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_node || 'false' }}
|
||||
OPENCLAW_CI_RUN_MACOS: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_macos || 'false' }}
|
||||
OPENCLAW_CI_RUN_IOS_BUILD: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_ios_build || 'false' }}
|
||||
OPENCLAW_CI_RUN_ANDROID: ${{ github.event_name == 'workflow_dispatch' && (inputs.release_gate || inputs.include_android) && 'true' || steps.changed_scope.outputs.run_android || 'false' }}
|
||||
OPENCLAW_CI_RUN_WINDOWS: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_windows || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_ONLY: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_only || 'false' }}
|
||||
@@ -251,6 +249,7 @@ jobs:
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const createMatrix = (include) => ({ include });
|
||||
const outputPath = process.env.GITHUB_OUTPUT;
|
||||
const isCanonicalRepository = process.env.OPENCLAW_CI_REPOSITORY === "openclaw/openclaw";
|
||||
@@ -268,8 +267,6 @@ jobs:
|
||||
const runPluginContractShards = runNodeFull || runNodeFastPluginContracts;
|
||||
const runMacos =
|
||||
parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly && isCanonicalRepository;
|
||||
const runIosBuild =
|
||||
parseBoolean(process.env.OPENCLAW_CI_RUN_IOS_BUILD) && !docsOnly && isCanonicalRepository;
|
||||
const runAndroid =
|
||||
parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly && isCanonicalRepository;
|
||||
const runWindows =
|
||||
@@ -284,7 +281,6 @@ jobs:
|
||||
if (runNodeFull) {
|
||||
checksFastCoreTasks.push(
|
||||
{ check_name: "checks-fast-bundled-protocol", runtime: "node", task: "bundled-protocol" },
|
||||
{ check_name: "QA Smoke CI", runtime: "node", task: "qa-smoke-ci" },
|
||||
{ check_name: "checks-fast-bun-launcher", runtime: "bun", task: "bun-launcher" },
|
||||
);
|
||||
} else {
|
||||
@@ -365,7 +361,6 @@ jobs:
|
||||
runMacos ? [{ check_name: "macos-node", runtime: "node", task: "test" }] : [],
|
||||
),
|
||||
run_macos_swift: runMacos,
|
||||
run_ios_build: runIosBuild,
|
||||
run_android_job: runAndroid,
|
||||
android_matrix: createMatrix(
|
||||
runAndroid
|
||||
@@ -848,32 +843,6 @@ jobs:
|
||||
path: .local/gateway-watch-regression/
|
||||
retention-days: 7
|
||||
|
||||
native-i18n:
|
||||
permissions:
|
||||
contents: read
|
||||
needs: [preflight]
|
||||
if: ${{ !cancelled() && always() && (needs.preflight.outputs.run_macos == 'true' || needs.preflight.outputs.run_android == 'true' || needs.preflight.outputs.run_node == 'true') }}
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Check native app i18n inventory
|
||||
run: pnpm native:i18n:check
|
||||
|
||||
- name: Check Android app i18n resources
|
||||
if: needs.preflight.outputs.run_android == 'true'
|
||||
run: pnpm android:i18n:check
|
||||
|
||||
checks-fast-core:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -884,7 +853,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 12
|
||||
max-parallel: 8
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -948,26 +917,6 @@ jobs:
|
||||
pnpm test:bundled
|
||||
pnpm protocol:check
|
||||
;;
|
||||
qa-smoke-ci)
|
||||
output_dir=".artifacts/qa-e2e/smoke-ci-profile"
|
||||
export OPENCLAW_BUILD_PRIVATE_QA=1
|
||||
export OPENCLAW_ENABLE_PRIVATE_QA_CLI=1
|
||||
export OPENCLAW_DISABLE_BUNDLED_PLUGINS=0
|
||||
export OPENCLAW_QA_REDACT_PUBLIC_METADATA=1
|
||||
export OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS=180000
|
||||
NODE_OPTIONS=--max-old-space-size=8192 node scripts/build-all.mjs qaRuntime
|
||||
qa_exit_code=0
|
||||
pnpm openclaw qa run \
|
||||
--repo-root . \
|
||||
--qa-profile smoke-ci \
|
||||
--concurrency 8 \
|
||||
--output-dir "$output_dir" || qa_exit_code=$?
|
||||
echo "QA smoke profile evidence: \`${output_dir}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$qa_exit_code" -ne 0 ]; then
|
||||
echo "::error title=QA smoke profile failed::smoke-ci exited ${qa_exit_code}; evidence upload will still run"
|
||||
exit "$qa_exit_code"
|
||||
fi
|
||||
;;
|
||||
contracts-plugins-ci-routing)
|
||||
pnpm test:contracts:plugins
|
||||
pnpm test src/commands/status.scan-result.test.ts src/scripts/ci-changed-scope.test.ts test/scripts/changed-lanes.test.ts test/scripts/ci-workflow-guards.test.ts test/scripts/run-vitest.test.ts test/scripts/test-projects.test.ts
|
||||
@@ -984,15 +933,6 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Upload QA smoke profile evidence
|
||||
if: always() && matrix.task == 'qa-smoke-ci'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: qa-smoke-profile-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: .artifacts/qa-e2e/smoke-ci-profile/
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
checks-fast-plugin-contracts-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -1003,7 +943,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 12
|
||||
max-parallel: 8
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.plugin_contracts_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1084,7 +1024,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 12
|
||||
max-parallel: 8
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.channel_contracts_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1238,8 +1178,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# The canonical main path waits for the admission debounce above, so
|
||||
# widen this large matrix within the current runner-registration budget.
|
||||
max-parallel: 24
|
||||
# modestly widen this large matrix without recreating registration bursts.
|
||||
max-parallel: 16
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_core_nondist_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1377,7 +1317,7 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 12
|
||||
max-parallel: 8
|
||||
matrix:
|
||||
include:
|
||||
- check_name: check-guards
|
||||
@@ -1519,7 +1459,7 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 12
|
||||
max-parallel: 8
|
||||
matrix:
|
||||
include:
|
||||
- check_name: check-additional-boundaries-a
|
||||
@@ -2222,76 +2162,6 @@ jobs:
|
||||
done
|
||||
exit 1
|
||||
|
||||
ios-build:
|
||||
permissions:
|
||||
contents: read
|
||||
name: "ios-build"
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_ios_build == 'true'
|
||||
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'macos-26' || (github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git init "$GITHUB_WORKSPACE"
|
||||
git -C "$GITHUB_WORKSPACE" config gc.auto 0
|
||||
git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git"
|
||||
fetch_timeout_seconds=90
|
||||
fetch_checkout_ref() {
|
||||
git -C "$GITHUB_WORKSPACE" \
|
||||
-c protocol.version=2 \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/checkout" &
|
||||
local fetch_pid="$!"
|
||||
local elapsed=0
|
||||
while kill -0 "$fetch_pid" 2>/dev/null; do
|
||||
if [ "$elapsed" -ge "$fetch_timeout_seconds" ]; then
|
||||
kill -TERM "$fetch_pid" 2>/dev/null || true
|
||||
sleep 10
|
||||
kill -KILL "$fetch_pid" 2>/dev/null || true
|
||||
wait "$fetch_pid" || true
|
||||
return 124
|
||||
fi
|
||||
sleep 1
|
||||
elapsed=$((elapsed + 1))
|
||||
done
|
||||
wait "$fetch_pid"
|
||||
}
|
||||
fetch_checkout_ref
|
||||
git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout
|
||||
|
||||
- name: Select Xcode 26
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for xcode_app in /Applications/Xcode_26.5.app /Applications/Xcode-26.5.0.app; do
|
||||
if [ -d "$xcode_app/Contents/Developer" ]; then
|
||||
sudo xcode-select -s "$xcode_app/Contents/Developer"
|
||||
break
|
||||
fi
|
||||
done
|
||||
xcodebuild -version
|
||||
xcode_version="$(xcodebuild -version | awk 'NR == 1 { print $2 }')"
|
||||
if [[ "$xcode_version" != 26.* ]]; then
|
||||
echo "error: expected Xcode 26.x, got $xcode_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
swift --version
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Install iOS Swift tooling
|
||||
run: brew install xcodegen swiftlint swiftformat
|
||||
|
||||
- name: Build iOS app
|
||||
run: pnpm ios:build
|
||||
|
||||
android:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -2443,10 +2313,8 @@ jobs:
|
||||
- checks-windows
|
||||
- macos-node
|
||||
- macos-swift
|
||||
- ios-build
|
||||
- android
|
||||
# Re-enable this job when we want to collect CI timing data for timing optimization.
|
||||
if: ${{ false && !cancelled() && always() && github.event_name != 'push' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
|
||||
if: ${{ !cancelled() && always() && github.event_name != 'push' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
|
||||
32
.github/workflows/codeql-critical-quality.yml
vendored
32
.github/workflows/codeql-critical-quality.yml
vendored
@@ -152,7 +152,7 @@ jobs:
|
||||
quality-shards:
|
||||
name: Select Critical Quality shards
|
||||
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
agent: ${{ steps.detect.outputs.agent }}
|
||||
@@ -333,7 +333,7 @@ jobs:
|
||||
name: Critical Quality (core-auth-secrets)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.core_auth_secrets == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'core-auth-secrets') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
name: Critical Quality (config-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.config == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'config-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -379,7 +379,7 @@ jobs:
|
||||
name: Critical Quality (gateway-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.gateway == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'gateway-runtime-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -402,7 +402,7 @@ jobs:
|
||||
name: Critical Quality (channel-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.channel == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'channel-runtime-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -425,7 +425,7 @@ jobs:
|
||||
name: Critical Quality (network-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.network_runtime == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'network-runtime-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -509,7 +509,7 @@ jobs:
|
||||
name: Critical Quality (agent-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.agent == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'agent-runtime-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -532,7 +532,7 @@ jobs:
|
||||
name: Critical Quality (mcp-process-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.mcp_process == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'mcp-process-runtime-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -555,7 +555,7 @@ jobs:
|
||||
name: Critical Quality (memory-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.memory == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'memory-runtime-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -578,7 +578,7 @@ jobs:
|
||||
name: Critical Quality (session-diagnostics-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.session_diagnostics == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'session-diagnostics-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -601,7 +601,7 @@ jobs:
|
||||
name: Critical Quality (plugin-sdk-reply-runtime)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.plugin_sdk_reply == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'plugin-sdk-reply-runtime') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -624,7 +624,7 @@ jobs:
|
||||
name: Critical Quality (provider-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.provider == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'provider-runtime-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -646,7 +646,7 @@ jobs:
|
||||
ui-control-plane:
|
||||
name: Critical Quality (ui-control-plane)
|
||||
if: ${{ github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || inputs.profile == 'all') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -668,7 +668,7 @@ jobs:
|
||||
web-media-runtime-boundary:
|
||||
name: Critical Quality (web-media-runtime-boundary)
|
||||
if: ${{ github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || inputs.profile == 'all') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -691,7 +691,7 @@ jobs:
|
||||
name: Critical Quality (plugin-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.plugin == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'plugin-boundary') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -714,7 +714,7 @@ jobs:
|
||||
name: Critical Quality (plugin-sdk-package-contract)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.plugin_sdk_package == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'plugin-sdk-package-contract') }}
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
38
.github/workflows/crabbox-hydrate.yml
vendored
38
.github/workflows/crabbox-hydrate.yml
vendored
@@ -171,19 +171,10 @@ jobs:
|
||||
set -euo pipefail
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
link_node_tool() {
|
||||
local tool="$1"
|
||||
local source="$node_bin/$tool"
|
||||
local target="/usr/local/bin/$tool"
|
||||
if [ -e "$target" ] && [ "$(readlink -f "$source")" = "$(readlink -f "$target")" ]; then
|
||||
return
|
||||
fi
|
||||
sudo ln -sf "$source" "$target"
|
||||
}
|
||||
link_node_tool node
|
||||
link_node_tool npm
|
||||
link_node_tool npx
|
||||
link_node_tool corepack
|
||||
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
||||
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
||||
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
|
||||
#!/usr/bin/env bash
|
||||
exec /usr/local/bin/corepack pnpm "$@"
|
||||
@@ -499,7 +490,7 @@ jobs:
|
||||
if (-not $env:CRABBOX_ID -or $env:CRABBOX_ID -notmatch '^[A-Za-z0-9._-]+$') {
|
||||
Write-Error "Invalid crabbox_id"
|
||||
}
|
||||
$actionsRoot = "C:\ProgramData\crabbox\actions"
|
||||
$actionsRoot = Join-Path $HOME ".crabbox\actions"
|
||||
New-Item -ItemType Directory -Force $actionsRoot | Out-Null
|
||||
$state = Join-Path $actionsRoot "$env:CRABBOX_ID.env"
|
||||
$envFile = Join-Path $actionsRoot "$env:CRABBOX_ID.env.ps1"
|
||||
@@ -555,7 +546,7 @@ jobs:
|
||||
if ($env:CRABBOX_KEEP_ALIVE_MINUTES -match '^[0-9]+$') {
|
||||
$minutes = [int]$env:CRABBOX_KEEP_ALIVE_MINUTES
|
||||
}
|
||||
$stop = Join-Path "C:\ProgramData\crabbox\actions" "$env:CRABBOX_ID.stop"
|
||||
$stop = Join-Path $HOME ".crabbox\actions\$env:CRABBOX_ID.stop"
|
||||
$deadline = (Get-Date).AddMinutes($minutes)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
if (Test-Path $stop) {
|
||||
@@ -593,19 +584,10 @@ jobs:
|
||||
fi
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
link_node_tool() {
|
||||
local tool="$1"
|
||||
local source="$node_bin/$tool"
|
||||
local target="/usr/local/bin/$tool"
|
||||
if [ -e "$target" ] && [ "$(readlink -f "$source")" = "$(readlink -f "$target")" ]; then
|
||||
return
|
||||
fi
|
||||
sudo ln -sf "$source" "$target"
|
||||
}
|
||||
link_node_tool node
|
||||
link_node_tool npm
|
||||
link_node_tool npx
|
||||
link_node_tool corepack
|
||||
sudo ln -sf "$node_bin/node" /usr/local/bin/node
|
||||
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
||||
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
|
||||
#!/usr/bin/env bash
|
||||
exec /usr/local/bin/corepack pnpm "$@"
|
||||
|
||||
122
.github/workflows/maturity-scorecard.yml
vendored
122
.github/workflows/maturity-scorecard.yml
vendored
@@ -12,40 +12,6 @@ on:
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
expected_sha:
|
||||
description: Optional full SHA that ref must resolve to
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
qa_evidence_run_id:
|
||||
description: Optional workflow run id containing qa-evidence.json
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
ref:
|
||||
description: OpenClaw branch, tag, or SHA containing the maturity score source
|
||||
required: true
|
||||
type: string
|
||||
expected_sha:
|
||||
description: Optional full SHA that ref must resolve to
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
description: OpenAI API key used by live QA profile scenarios
|
||||
required: true
|
||||
OPENCLAW_MATURITY_SCORECARD_AGENT_OPENAI_API_KEY:
|
||||
description: Optional OpenAI API key used by maturity scorecard agent steps
|
||||
required: false
|
||||
GH_APP_PRIVATE_KEY:
|
||||
description: Optional GitHub App private key for generated docs PR creation
|
||||
required: false
|
||||
GH_APP_PRIVATE_KEY_FALLBACK:
|
||||
description: Optional fallback GitHub App private key for generated docs PR creation
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -77,25 +43,14 @@ jobs:
|
||||
- name: Validate selected ref
|
||||
id: validate
|
||||
env:
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
INPUT_REF: ${{ inputs.ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
selected_revision="$(git rev-parse HEAD)"
|
||||
expected_sha="${EXPECTED_SHA,,}"
|
||||
trusted_reason=""
|
||||
|
||||
if [[ -n "${expected_sha// }" && ! "$expected_sha" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "expected_sha must be a full 40-character SHA; got: ${EXPECTED_SHA}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${expected_sha// }" && "${selected_revision,,}" != "$expected_sha" ]]; then
|
||||
echo "Ref '${INPUT_REF}' resolved to ${selected_revision}, expected ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
|
||||
if git merge-base --is-ancestor "$selected_revision" refs/remotes/origin/main; then
|
||||
@@ -132,8 +87,7 @@ jobs:
|
||||
if: ${{ inputs.qa_evidence_run_id == '' }}
|
||||
uses: ./.github/workflows/qa-profile-evidence.yml
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
expected_sha: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
qa_profile: all
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
@@ -264,67 +218,6 @@ jobs:
|
||||
}
|
||||
NODE
|
||||
|
||||
- name: Ensure maturity scorecard agent key exists
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENCLAW_MATURITY_SCORECARD_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
||||
echo "Missing OPENCLAW_MATURITY_SCORECARD_AGENT_OPENAI_API_KEY or OPENAI_API_KEY secret." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run Codex maturity scorecard agent
|
||||
uses: openai/codex-action@e0fdf01220eb9a88167c4898839d273e3f2609d1
|
||||
env:
|
||||
MATURITY_EVIDENCE_DIR: .artifacts/maturity-evidence
|
||||
MATURITY_SCORES_PATH: qa/maturity-scores.yaml
|
||||
MATURITY_TAXONOMY_PATH: taxonomy.yaml
|
||||
with:
|
||||
openai-api-key: ${{ secrets.OPENCLAW_MATURITY_SCORECARD_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
||||
prompt-file: .github/codex/prompts/maturity-scorecard-agent.md
|
||||
model: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
|
||||
effort: high
|
||||
sandbox: workspace-write
|
||||
safety-strategy: drop-sudo
|
||||
|
||||
- name: Enforce focused maturity score patch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git restore --staged :/
|
||||
|
||||
allowed='^qa/maturity-scores\.yaml$'
|
||||
bad_tracked="$(
|
||||
git diff --name-only HEAD -- | while IFS= read -r path; do
|
||||
if [[ ! "$path" =~ $allowed ]]; then
|
||||
printf '%s\n' "$path"
|
||||
fi
|
||||
done
|
||||
)"
|
||||
if [[ -n "$bad_tracked" ]]; then
|
||||
echo "Maturity scorecard agent touched forbidden tracked paths:"
|
||||
printf '%s\n' "$bad_tracked"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bad_untracked="$(
|
||||
git ls-files --others --exclude-standard | while IFS= read -r path; do
|
||||
if [[ "$path" != "qa/maturity-scores.yaml" ]]; then
|
||||
printf '%s\n' "$path"
|
||||
fi
|
||||
done
|
||||
)"
|
||||
if [[ -n "$bad_untracked" ]]; then
|
||||
echo "Maturity scorecard agent created forbidden untracked paths:"
|
||||
printf '%s\n' "$bad_untracked"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f qa/maturity-scores.yaml ]]; then
|
||||
echo "Maturity scorecard agent must produce qa/maturity-scores.yaml." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Validate maturity score sources
|
||||
run: |
|
||||
node --import tsx --input-type=module <<'NODE'
|
||||
@@ -367,7 +260,6 @@ jobs:
|
||||
--strict-inputs
|
||||
|
||||
- name: Create generated docs PR app token
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
id: app-token
|
||||
continue-on-error: true
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
|
||||
@@ -378,7 +270,7 @@ jobs:
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Create generated docs PR fallback app token
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && steps.app-token.outcome == 'failure' }}
|
||||
if: ${{ steps.app-token.outcome == 'failure' }}
|
||||
id: app-token-fallback
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
|
||||
with:
|
||||
@@ -388,7 +280,6 @@ jobs:
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Open generated docs PR
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
QA_EVIDENCE_RUN_ID: ${{ inputs.qa_evidence_run_id }}
|
||||
@@ -400,7 +291,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$(git status --porcelain -- qa/maturity-scores.yaml docs/maturity/scorecard.md docs/maturity/taxonomy.md)" ]]; then
|
||||
if [[ -z "$(git status --porcelain -- qa/maturity-scores.yaml docs/maturity-scores.yaml docs/maturity/scorecard.md docs/maturity/taxonomy.md)" ]]; then
|
||||
{
|
||||
echo
|
||||
echo "- Pull request: skipped; generated scorecard matches selected ref"
|
||||
@@ -420,6 +311,9 @@ jobs:
|
||||
git fetch --no-tags --depth=1 origin "refs/heads/${branch}:refs/remotes/origin/${branch}" || true
|
||||
git switch -C "$branch"
|
||||
git add qa/maturity-scores.yaml docs/maturity/scorecard.md docs/maturity/taxonomy.md
|
||||
if git ls-files --error-unmatch docs/maturity-scores.yaml >/dev/null 2>&1 || [[ -e docs/maturity-scores.yaml ]]; then
|
||||
git add docs/maturity-scores.yaml
|
||||
fi
|
||||
git commit -m "docs: update maturity scorecard"
|
||||
git push --force-with-lease origin "$branch"
|
||||
|
||||
@@ -428,14 +322,14 @@ jobs:
|
||||
cat > "$body_file" <<BODY
|
||||
## Summary
|
||||
|
||||
- render maturity scorecard docs from \`qa/maturity-scores.yaml\` and full taxonomy QA evidence
|
||||
- render maturity scorecard docs from \`qa/maturity-scores.yaml\` and release QA evidence
|
||||
- maturity source ref: ${REF_INPUT}
|
||||
- QA evidence run: ${evidence_run_id}
|
||||
|
||||
## Verification
|
||||
|
||||
- QA Lab maturity score validation passed
|
||||
- Maturity scorecard workflow rendered docs from all profile qa-evidence.json artifacts with strict inputs
|
||||
- Maturity scorecard workflow rendered docs from release profile qa-evidence.json artifacts with strict inputs
|
||||
BODY
|
||||
|
||||
pr_url="$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // ""')"
|
||||
|
||||
@@ -609,6 +609,7 @@ jobs:
|
||||
requires_repo_e2e: true
|
||||
requires_live_suites: false
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_E2E_WORKERS: "1"
|
||||
OPENCLAW_VITEST_MAX_WORKERS: "1"
|
||||
steps:
|
||||
@@ -642,74 +643,9 @@ jobs:
|
||||
set -euo pipefail
|
||||
case "${{ matrix.suite_id }}" in
|
||||
openshell-e2e)
|
||||
echo "OPENCLAW_E2E_OPENSHELL_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Install OpenShell CLI
|
||||
if: |
|
||||
(inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id) &&
|
||||
matrix.suite_id == 'openshell-e2e'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export OPENSHELL_VERSION=v0.0.68
|
||||
curl -LsSf https://raw.githubusercontent.com/NVIDIA/OpenShell/d64542f69d06694cbd203b64929d286dd0533bbb/install.sh | sh
|
||||
openshell --version
|
||||
|
||||
- name: Bootstrap OpenShell gateway
|
||||
if: |
|
||||
(inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id) &&
|
||||
matrix.suite_id == 'openshell-e2e'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mtls_dir="$HOME/.config/openshell/gateways/openshell/mtls"
|
||||
gateway_tls_dir="$RUNNER_TEMP/openshell-gateway-certs"
|
||||
fallback_pid=""
|
||||
if ! openshell --gateway openshell sandbox list >/dev/null 2>&1; then
|
||||
rm -rf "$gateway_tls_dir"
|
||||
openshell-gateway generate-certs \
|
||||
--output-dir "$gateway_tls_dir" \
|
||||
--server-san 127.0.0.1 \
|
||||
--server-san localhost \
|
||||
--server-san host.openshell.internal
|
||||
rm -rf "$mtls_dir"
|
||||
mkdir -p "$mtls_dir"
|
||||
cp "$gateway_tls_dir/ca.crt" "$mtls_dir/ca.crt"
|
||||
cp "$gateway_tls_dir/client/tls.crt" "$mtls_dir/tls.crt"
|
||||
cp "$gateway_tls_dir/client/tls.key" "$mtls_dir/tls.key"
|
||||
openshell gateway remove openshell >/dev/null 2>&1 || true
|
||||
OPENSHELL_LOCAL_TLS_DIR="$gateway_tls_dir" nohup openshell-gateway \
|
||||
--bind-address 0.0.0.0 \
|
||||
--port 17670 \
|
||||
--drivers docker \
|
||||
--tls-cert "$gateway_tls_dir/server/tls.crt" \
|
||||
--tls-key "$gateway_tls_dir/server/tls.key" \
|
||||
--tls-client-ca "$mtls_dir/ca.crt" \
|
||||
>"$RUNNER_TEMP/openshell-gateway.log" 2>&1 &
|
||||
fallback_pid=$!
|
||||
echo "OPENCLAW_OPENSHELL_FALLBACK_PID=$fallback_pid" >> "$GITHUB_ENV"
|
||||
for _ in $(seq 1 30); do
|
||||
if openshell gateway add --local --name openshell https://127.0.0.1:17670; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
openshell gateway select openshell
|
||||
for _ in $(seq 1 60); do
|
||||
if openshell --gateway openshell sandbox list >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
if [[ -z "$fallback_pid" ]]; then
|
||||
echo "OPENCLAW_OPENSHELL_FALLBACK_PID=" >> "$GITHUB_ENV"
|
||||
fi
|
||||
openshell --gateway openshell sandbox list >/dev/null
|
||||
openshell gateway list
|
||||
|
||||
- name: Validate suite credentials
|
||||
if: inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id
|
||||
shell: bash
|
||||
@@ -729,15 +665,6 @@ jobs:
|
||||
(inputs.live_suite_filter == '' || inputs.live_suite_filter == matrix.suite_id)
|
||||
run: ${{ matrix.command }}
|
||||
|
||||
- name: Stop fallback OpenShell gateway
|
||||
if: always() && matrix.suite_id == 'openshell-e2e'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${OPENCLAW_OPENSHELL_FALLBACK_PID:-}" ]]; then
|
||||
kill "$OPENCLAW_OPENSHELL_FALLBACK_PID" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
validate_docker_e2e:
|
||||
needs: [validate_selected_ref, prepare_docker_e2e_image, plan_release_workflow_matrices]
|
||||
if: inputs.include_release_path_suites && inputs.docker_lanes == '' && needs.plan_release_workflow_matrices.outputs.docker_e2e_count != '0'
|
||||
|
||||
30
.github/workflows/openclaw-performance.yml
vendored
30
.github/workflows/openclaw-performance.yml
vendored
@@ -151,39 +151,11 @@ jobs:
|
||||
echo "present=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Resolve OpenClaw target ref
|
||||
id: target
|
||||
if: steps.lane.outputs.run == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_REF_INPUT: ${{ inputs.target_ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
requested="${TARGET_REF_INPUT:-}"
|
||||
if [[ -z "$requested" ]]; then
|
||||
echo "checkout_ref=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
|
||||
echo "tested_ref=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
encoded_ref="$(node -e 'process.stdout.write(encodeURIComponent(process.argv[1]))' "$requested")"
|
||||
if ! resolved_sha="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${encoded_ref}" --jq '.sha')"; then
|
||||
echo "::error::Unable to resolve OpenClaw target_ref '${requested}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "$resolved_sha" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "::error::OpenClaw target_ref '${requested}' resolved to invalid SHA '${resolved_sha}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "checkout_ref=${resolved_sha}" >> "$GITHUB_OUTPUT"
|
||||
echo "tested_ref=${requested}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout OpenClaw
|
||||
if: steps.lane.outputs.run == 'true'
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ steps.target.outputs.checkout_ref }}
|
||||
ref: ${{ inputs.target_ref || github.ref }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
52
.github/workflows/openclaw-release-checks.yml
vendored
52
.github/workflows/openclaw-release-checks.yml
vendored
@@ -44,11 +44,6 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
run_maturity_scorecard:
|
||||
description: Render advisory maturity scorecard release docs; default release checks rely on dedicated package, QA, live, and E2E gates
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
rerun_group:
|
||||
description: Release check group to run
|
||||
required: false
|
||||
@@ -111,7 +106,6 @@ jobs:
|
||||
mode: ${{ steps.inputs.outputs.mode }}
|
||||
release_profile: ${{ steps.inputs.outputs.release_profile }}
|
||||
run_release_soak: ${{ steps.inputs.outputs.run_release_soak }}
|
||||
run_maturity_scorecard: ${{ steps.inputs.outputs.run_maturity_scorecard }}
|
||||
rerun_group: ${{ steps.inputs.outputs.rerun_group }}
|
||||
live_suite_filter: ${{ steps.inputs.outputs.live_suite_filter }}
|
||||
cross_os_suite_filter: ${{ steps.inputs.outputs.cross_os_suite_filter }}
|
||||
@@ -285,7 +279,6 @@ jobs:
|
||||
RELEASE_MODE_INPUT: ${{ inputs.mode }}
|
||||
RELEASE_PROFILE_INPUT: ${{ inputs.release_profile }}
|
||||
RELEASE_RUN_RELEASE_SOAK_INPUT: ${{ inputs.run_release_soak }}
|
||||
RELEASE_RUN_MATURITY_SCORECARD_INPUT: ${{ inputs.run_maturity_scorecard }}
|
||||
RELEASE_RERUN_GROUP_INPUT: ${{ inputs.rerun_group }}
|
||||
RELEASE_LIVE_SUITE_FILTER_INPUT: ${{ inputs.live_suite_filter }}
|
||||
RELEASE_CROSS_OS_SUITE_FILTER_INPUT: ${{ inputs.cross_os_suite_filter }}
|
||||
@@ -326,12 +319,6 @@ jobs:
|
||||
else
|
||||
run_release_soak=true
|
||||
fi
|
||||
run_maturity_scorecard="$(printf '%s' "$RELEASE_RUN_MATURITY_SCORECARD_INPUT" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "$run_maturity_scorecard" != "true" && "$run_maturity_scorecard" != "1" && "$run_maturity_scorecard" != "yes" ]]; then
|
||||
run_maturity_scorecard=false
|
||||
else
|
||||
run_maturity_scorecard=true
|
||||
fi
|
||||
release_profile="$RELEASE_PROFILE_INPUT"
|
||||
if [[ "$release_profile" == "minimum" ]]; then
|
||||
release_profile=beta
|
||||
@@ -435,7 +422,6 @@ jobs:
|
||||
printf 'mode=%s\n' "$RELEASE_MODE_INPUT"
|
||||
printf 'release_profile=%s\n' "$release_profile"
|
||||
printf 'run_release_soak=%s\n' "$run_release_soak"
|
||||
printf 'run_maturity_scorecard=%s\n' "$run_maturity_scorecard"
|
||||
printf 'rerun_group=%s\n' "$RELEASE_RERUN_GROUP_INPUT"
|
||||
printf 'live_suite_filter=%s\n' "$RELEASE_LIVE_SUITE_FILTER_INPUT"
|
||||
printf 'cross_os_suite_filter=%s\n' "$RELEASE_CROSS_OS_SUITE_FILTER_INPUT"
|
||||
@@ -458,7 +444,6 @@ jobs:
|
||||
RELEASE_MODE: ${{ inputs.mode }}
|
||||
RELEASE_PROFILE: ${{ steps.inputs.outputs.release_profile }}
|
||||
RUN_RELEASE_SOAK: ${{ steps.inputs.outputs.run_release_soak }}
|
||||
RUN_MATURITY_SCORECARD: ${{ steps.inputs.outputs.run_maturity_scorecard }}
|
||||
RELEASE_RERUN_GROUP: ${{ inputs.rerun_group }}
|
||||
RELEASE_LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
|
||||
RELEASE_CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
|
||||
@@ -476,7 +461,6 @@ jobs:
|
||||
echo "- Cross-OS mode: \`${RELEASE_MODE}\`"
|
||||
echo "- Release profile: \`${RELEASE_PROFILE}\`"
|
||||
echo "- Release soak lanes: \`${RUN_RELEASE_SOAK}\`"
|
||||
echo "- Maturity scorecard docs: \`${RUN_MATURITY_SCORECARD}\`"
|
||||
echo "- Rerun group: \`${RELEASE_RERUN_GROUP}\`"
|
||||
if [[ -n "${RELEASE_LIVE_SUITE_FILTER// }" ]]; then
|
||||
echo "- Live suite filter: \`${RELEASE_LIVE_SUITE_FILTER}\`"
|
||||
@@ -783,20 +767,6 @@ jobs:
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
|
||||
maturity_scorecard_release_checks:
|
||||
name: Render maturity scorecard release docs
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","qa"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.run_maturity_scorecard == 'true'
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
uses: ./.github/workflows/maturity-scorecard.yml
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
expected_sha: ${{ needs.resolve_target.outputs.revision }}
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
qa_lab_parity_lane_release_checks:
|
||||
name: Run QA Lab parity lane (${{ matrix.lane }})
|
||||
needs: [resolve_target]
|
||||
@@ -883,7 +853,7 @@ jobs:
|
||||
name: release-qa-parity-${{ matrix.lane }}-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -989,7 +959,7 @@ jobs:
|
||||
name: release-qa-parity-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -1161,7 +1131,7 @@ jobs:
|
||||
name: release-qa-runtime-parity-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -1271,13 +1241,13 @@ jobs:
|
||||
--output .artifacts/qa-e2e/runtime-parity-standard-report/qa-runtime-tool-coverage-report.md
|
||||
|
||||
- name: Upload runtime tool coverage artifacts
|
||||
if: ${{ always() && steps.verify_runtime_parity_status.outputs.ready == 'true' }}
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: release-qa-runtime-tool-coverage-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/runtime-parity-standard-report/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
qa_live_matrix_release_checks:
|
||||
name: Run QA Lab live Matrix lane
|
||||
@@ -1357,7 +1327,7 @@ jobs:
|
||||
name: release-qa-live-matrix-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -1497,7 +1467,7 @@ jobs:
|
||||
name: release-qa-live-telegram-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -1637,7 +1607,7 @@ jobs:
|
||||
name: release-qa-live-discord-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -1780,7 +1750,7 @@ jobs:
|
||||
name: release-qa-live-whatsapp-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -1920,7 +1890,7 @@ jobs:
|
||||
name: release-qa-live-slack-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Record advisory status
|
||||
if: always()
|
||||
@@ -1976,7 +1946,6 @@ jobs:
|
||||
- docker_e2e_release_checks
|
||||
- package_acceptance_release_checks
|
||||
- qa_lab_parity_lane_release_checks
|
||||
- maturity_scorecard_release_checks
|
||||
- qa_lab_parity_report_release_checks
|
||||
- qa_lab_runtime_parity_release_checks
|
||||
- runtime_tool_coverage_release_checks
|
||||
@@ -2062,7 +2031,6 @@ jobs:
|
||||
"docker_e2e_release_checks=${{ needs.docker_e2e_release_checks.result }}" \
|
||||
"package_acceptance_release_checks=${{ needs.package_acceptance_release_checks.result }}" \
|
||||
"qa_lab_parity_lane_release_checks=${{ needs.qa_lab_parity_lane_release_checks.result }}" \
|
||||
"maturity_scorecard_release_checks=${{ needs.maturity_scorecard_release_checks.result }}" \
|
||||
"qa_lab_parity_report_release_checks=${{ needs.qa_lab_parity_report_release_checks.result }}" \
|
||||
"qa_lab_runtime_parity_release_checks=${{ needs.qa_lab_runtime_parity_release_checks.result }}" \
|
||||
"runtime_tool_coverage_release_checks=${{ needs.runtime_tool_coverage_release_checks.result }}" \
|
||||
|
||||
@@ -1466,9 +1466,9 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload postpublish evidence
|
||||
if: ${{ always() && inputs.publish_openclaw_npm }}
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: openclaw-release-postpublish-evidence-${{ inputs.tag }}
|
||||
path: ${{ runner.temp }}/openclaw-release-postpublish-evidence
|
||||
if-no-files-found: error
|
||||
if-no-files-found: ignore
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
name: Plugin Init Scaffold Validation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/plugin-init-scaffold-validation.yml"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "scripts/validate-plugin-init-provider-scaffold.ts"
|
||||
- "src/cli/plugins-authoring-command.ts"
|
||||
- "src/cli/plugins-authoring-command.test.ts"
|
||||
- "src/cli/plugins-cli.ts"
|
||||
- "src/plugin-sdk/**"
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- ".github/workflows/plugin-init-scaffold-validation.yml"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "scripts/validate-plugin-init-provider-scaffold.ts"
|
||||
- "src/cli/plugins-authoring-command.ts"
|
||||
- "src/cli/plugins-authoring-command.test.ts"
|
||||
- "src/cli/plugins-cli.ts"
|
||||
- "src/plugin-sdk/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
validate-provider-scaffold:
|
||||
name: Validate provider scaffold
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Generate and validate provider scaffold
|
||||
run: pnpm test:plugins:init-provider-scaffold
|
||||
12
.github/workflows/qa-profile-evidence.yml
vendored
12
.github/workflows/qa-profile-evidence.yml
vendored
@@ -18,7 +18,7 @@ on:
|
||||
qa_profile:
|
||||
description: Taxonomy QA profile id to run (for example release or all)
|
||||
required: true
|
||||
default: all
|
||||
default: release
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -89,13 +89,6 @@ jobs:
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
// Reusable workflow jobs inherit the caller event but run as
|
||||
// github-actions[bot]; selected ref validation still gates secrets.
|
||||
if (context.actor === "github-actions[bot]") {
|
||||
core.info("Skipping manual actor permission check for a reusable workflow call.");
|
||||
core.setOutput("authorized", "true");
|
||||
return;
|
||||
}
|
||||
if (context.eventName !== "workflow_dispatch") {
|
||||
core.info(`Skipping manual actor permission check for ${context.eventName}.`);
|
||||
core.setOutput("authorized", "true");
|
||||
@@ -250,9 +243,6 @@ jobs:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: node scripts/build-all.mjs qaRuntime
|
||||
|
||||
- name: Ensure Playwright Chromium
|
||||
run: node scripts/ensure-playwright-chromium.mjs
|
||||
|
||||
- name: Run QA profile
|
||||
id: run_profile
|
||||
env:
|
||||
|
||||
5
.github/workflows/sandbox-common-smoke.yml
vendored
5
.github/workflows/sandbox-common-smoke.yml
vendored
@@ -57,10 +57,11 @@ jobs:
|
||||
BASE_IMAGE="openclaw-sandbox-smoke-base:bookworm-slim" \
|
||||
TARGET_IMAGE="openclaw-sandbox-common-smoke:bookworm-slim" \
|
||||
PACKAGES="ca-certificates" \
|
||||
INSTALL_PNPM=0 \
|
||||
INSTALL_BUN=0 \
|
||||
INSTALL_BREW=0 \
|
||||
FINAL_USER=sandbox \
|
||||
scripts/sandbox-common-setup.sh
|
||||
|
||||
timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc \
|
||||
'set -e; test "$(id -un)" = sandbox; node --version; pnpm --version'
|
||||
u="$(timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc 'id -un')"
|
||||
test "$u" = "sandbox"
|
||||
|
||||
@@ -118,11 +118,11 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Tests in a normal source checkout: `pnpm test <path-or-filter> [vitest args...]`, `pnpm test:changed`, `pnpm test:serial`, `pnpm test:coverage`; never raw `vitest`.
|
||||
- If raw Vitest is unavoidable, use `vitest run ...`; bare `vitest ...` starts local watch mode and will not exit on its own.
|
||||
- Tests in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm test*`; use `node scripts/run-vitest.mjs <path-or-filter>` for tiny explicit-file proof, or Crabbox/Testbox for anything broader.
|
||||
- Checks/lint in a normal source checkout: `pnpm check:changed` delegates to Crabbox/Testbox; lanes: `pnpm changed:lanes --json`; staged/path-scoped: `pnpm check:changed --staged` or `pnpm check:changed -- <files...>`; full `pnpm check`/`pnpm lint` only when required.
|
||||
- Checks in a normal source checkout: `pnpm check:changed` delegates to Crabbox/Testbox; lanes: `pnpm changed:lanes --json`; staged: `pnpm check:changed --staged`; full: `pnpm check`.
|
||||
- Checks in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm check*`; use `node scripts/crabbox-wrapper.mjs run ... -- env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed` so pnpm runs inside Testbox, not locally.
|
||||
- Extension tests: `pnpm test:extensions`, `pnpm test extensions`, `pnpm test extensions/<id>`.
|
||||
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); never add `tsc --noEmit`, `typecheck`, `check:types`.
|
||||
- Formatting: `oxfmt`, not Prettier. Use repo wrappers (`pnpm format:*`, `scripts/run-oxlint.mjs`; full `pnpm lint:*` only when scope requires).
|
||||
- Formatting: `oxfmt`, not Prettier. Use repo wrappers (`pnpm format:*`, `pnpm lint:*`, `scripts/run-oxlint.mjs`).
|
||||
- Build before push when build output, packaging, lazy/module boundaries, dynamic imports, or published surfaces can change.
|
||||
|
||||
## Validation
|
||||
@@ -143,9 +143,6 @@ Skills own workflows; root owns hard policy and routing.
|
||||
|
||||
## GitHub / PRs
|
||||
|
||||
- Fresh GitHub items: read `CONTRIBUTING.md`, the issue chooser/form, PR template, and `.github/CODEOWNERS`; blank issues are disabled; preserve templates and evidence requirements.
|
||||
- Agent-authored/non-trivial work: create or reuse the issue first; tiny fixes may go direct. PRs use the template, link context, and keep durable problem/impact/evidence sections.
|
||||
- Route support to Discord and security through `SECURITY.md`. Use listed maintainer areas/`CODEOWNERS`; never guess mentions.
|
||||
- Use `$openclaw-pr-maintainer` immediately for maintainer-side OpenClaw issue/PR review, triage, duplicates, labels, comments, close, land, or evidence. Contributor PR creation/refresh follows the requested contributor workflow; linked refs alone do not require maintainer archive tooling.
|
||||
- Issue/PR start: `git status -sb`; if clean, `git pull --ff-only`; if dirty, yell before pull/rebase.
|
||||
- PR refs: `gh pr view/diff` or `gh api`, not web search. Prefer `gitcrawl` for maintainer discovery; missing/stale `gitcrawl` falls through to live `gh`, not contributor setup. Verify live with `gh` before mutation.
|
||||
|
||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -2,101 +2,6 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixes
|
||||
|
||||
- **WeChat account routing:** `startAccount` preserves session routing by resolving manifest channel account config from raw account keys with opaque provider ids, while still ignoring manifest account keys that normalize to blocked object keys. (#93686) Thanks @zhangguiping-xydt.
|
||||
|
||||
## 2026.6.10
|
||||
|
||||
Automatic fast mode starts short conversations quickly, then returns longer or fallback work to normal mode without losing visible state. Provider routing, channel progress, session identity, and trusted tool policies are more reliable, with smaller improvements spanning provider setup, diagnostics, and transcript tooling.
|
||||
|
||||
### Highlights
|
||||
|
||||
#### Automatic fast mode
|
||||
|
||||
- Adds [`/fast auto`](https://docs.openclaw.ai/tools/thinking) so short conversational calls can start quickly, while longer or fallback work returns to normal mode with the effective state still visible. [PR #85104](https://github.com/openclaw/openclaw/pull/85104), [Issue #85087](https://github.com/openclaw/openclaw/issues/85087). Thanks @alexph-dev and @vincentkoc.
|
||||
- Shows the effective automatic fast-mode state in status instead of reducing it to on/off, and avoids carrying a cleared Codex service-tier choice into later runs. [8845f2f](https://github.com/openclaw/openclaw/commit/8845f2fd6143becc37110ab5021dd5e1517f0cdc). Thanks @vincentkoc.
|
||||
- Keeps automatic fast-mode timing consistent when a turn switches to a fallback model. [075091d](https://github.com/openclaw/openclaw/commit/075091d0cab94053ff094268efc0acb225d514f4). Thanks @vincentkoc.
|
||||
- Keeps the original fast-mode timing and progress behavior when a live model switch retries a turn. [d1e190f](https://github.com/openclaw/openclaw/commit/d1e190fbe822ad6ae4e660ce376b60ec9fdb0fba). Thanks @vincentkoc.
|
||||
- Keeps automatic fast-mode progress and reset behavior distinct from explicit fast mode after a run switches modes. [20aec98](https://github.com/openclaw/openclaw/commit/20aec985545db7a24ea066e5bff1c47b789cbded). Thanks @vincentkoc.
|
||||
- Shows the effective fast-mode value in connected-agent sessions instead of the configured value, so status reflects what the session is actually using. [9509aa0](https://github.com/openclaw/openclaw/commit/9509aa063c0ef3e32be1516fcb0c23606b6d5c7b). Thanks @vincentkoc.
|
||||
- Keeps the effective automatic fast-mode setting visible through fallback transitions in connected-agent sessions. [7f5423c](https://github.com/openclaw/openclaw/commit/7f5423ca97174a3f16c211db54a6c96e5b3a6089). Thanks @vincentkoc.
|
||||
- Keeps automatic fast-mode timing and progress consistent when reply and [scheduled-agent runs](https://docs.openclaw.ai/automation/cron-jobs) retry or switch models. [6c29f88](https://github.com/openclaw/openclaw/commit/6c29f88913796bfe05696556cd82246670b126f0). Thanks @vincentkoc.
|
||||
- Keeps fast-mode cleanup and status consistent when a run switches between fallback models. [c4694f8](https://github.com/openclaw/openclaw/commit/c4694f84ffd52064f89609098cc4f8570fb72e1b). Thanks @vincentkoc.
|
||||
- Shows the automatic fast-mode reset only when fallback work is finished, so status messages match the end of the transition. [f4d93c8](https://github.com/openclaw/openclaw/commit/f4d93c855bff6930f5e5d739b95e0c2612ec4899). Thanks @vincentkoc.
|
||||
- Shows reset and delivery progress at the right time when auto-reply or other follow-up runs retry or leave automatic fast mode. [684e440](https://github.com/openclaw/openclaw/commit/684e44013778bd47d159e64b2595e4d09a92ebea). Thanks @vincentkoc.
|
||||
|
||||
### Channels and Messaging
|
||||
|
||||
#### Channel delivery and progress updates
|
||||
|
||||
- Prevents the next turn after a [scheduled message](https://docs.openclaw.ai/automation/cron-jobs) from losing what was delivered or whether delivery failed, so replies can use that context without exposing cron details in the channel. [PR #93580](https://github.com/openclaw/openclaw/pull/93580). Thanks @jalehman and @scotthuang.
|
||||
- Prevents streamed channel progress from dropping a repeated status that represents a separate step, so each meaningful step remains visible in the draft. [2d42e52](https://github.com/openclaw/openclaw/commit/2d42e52ac5513e0bd824b8a0e069db83e04bc056). Thanks @vincentkoc.
|
||||
- Prevents keyed streamed progress from staying on an older status, so viewers see the latest state instead of stale text. [8bb6472](https://github.com/openclaw/openclaw/commit/8bb6472c4de2eea06f1ba31d6ed679e2ac4581b0). Thanks @vincentkoc.
|
||||
|
||||
### Providers and Models
|
||||
|
||||
#### Provider model catalogs and reasoning controls
|
||||
|
||||
- Treats Zhipu/GLM overload responses as overloads, so a configured fallback is selected for the right reason instead of following the wrong failover path. [PR #93241](https://github.com/openclaw/openclaw/pull/93241), [Issue #93211](https://github.com/openclaw/openclaw/issues/93211). Thanks @0xghost42 and @zhengli0922.
|
||||
- Prevents Telegram, Slack, and Discord `/think` menus for live Ollama models from hiding supported levels, so users can choose valid reasoning settings without guessing. [PR #94067](https://github.com/openclaw/openclaw/pull/94067), [Issue #93835](https://github.com/openclaw/openclaw/issues/93835). Thanks @civiltox and @openperf.
|
||||
- Expands [`zai/glm-5.2` thinking choices](https://docs.openclaw.ai/tools/thinking) beyond binary on/off and sends high or max requests as the intended Z.AI reasoning effort. [PR #94136](https://github.com/openclaw/openclaw/pull/94136). Thanks @borclaw.
|
||||
- Prevents bundled [Z.ai GLM-5 models](https://docs.openclaw.ai/providers/zai) from falling through to OpenAI and producing misleading API-key errors, so they use Z.AI by default. [PR #94461](https://github.com/openclaw/openclaw/pull/94461), [Issue #94269](https://github.com/openclaw/openclaw/issues/94269). Thanks @chrysb and @pandah97.
|
||||
- Adds GLM-5.2 and Kimi K2.7 Code to the [OpenCode Go catalog](https://docs.openclaw.ai/providers/opencode-go) with current limits, so users can select the models from OpenClaw. [66f84a9](https://github.com/openclaw/openclaw/commit/66f84a9bf1082de26f92b2b3741cc2f34aba34fa). Thanks @samson1357924.
|
||||
- Corrects `kimi-k2.7-code` capability listings so OpenCode Go users are not offered unsupported video prompts when the model accepts text and images. [715dc71](https://github.com/openclaw/openclaw/commit/715dc718fc5a2a5d6f7e9ec16e0269382b726e83).
|
||||
|
||||
#### Provider plugin onboarding
|
||||
|
||||
- Prevents first-run setup from skipping the selected provider's credential prompt after plugin installation, so onboarding continues with that provider instead of falling back to OpenAI. [PR #95792](https://github.com/openclaw/openclaw/pull/95792), [Issue #95765](https://github.com/openclaw/openclaw/issues/95765). Thanks @snowzlmbot.
|
||||
|
||||
### Memory, Sessions, and State
|
||||
|
||||
#### Session transcript SDK helpers
|
||||
|
||||
- Adds a durable [session-transcript SDK contract](https://docs.openclaw.ai/plugins/sdk-runtime) so plugins can read, append, publish, and lock the intended transcript without treating [legacy file paths](https://docs.openclaw.ai/plugins/sdk-subpaths) as identity. [PR #95030](https://github.com/openclaw/openclaw/pull/95030). Thanks @jalehman.
|
||||
|
||||
#### Cross-channel session identity
|
||||
|
||||
- Prevents a shared direct-message [session](https://docs.openclaw.ai/concepts/session) from carrying the previous [channel's identity](https://docs.openclaw.ai/channels/channel-routing) after a switch, so status, reactions, threads, and message references target the current channel. [PR #95328](https://github.com/openclaw/openclaw/pull/95328), [Issue #95325](https://github.com/openclaw/openclaw/issues/95325). Thanks @gorkem2020, @jalehman, and @zengwen-dt.
|
||||
|
||||
### Gateway, Security, and Trust
|
||||
|
||||
#### Prompt context boundaries
|
||||
|
||||
- Keeps empty prompts separate from hook-added context during compaction or session reuse in [Copilot and Codex sessions](https://docs.openclaw.ai/plugins/copilot), so prompt boundaries remain consistent. [PR #94838](https://github.com/openclaw/openclaw/pull/94838). Thanks @vincentkoc.
|
||||
|
||||
#### Trusted tool policy enforcement
|
||||
|
||||
- Keeps [approval-sensitive Gateway and plugin tools](https://docs.openclaw.ai/plugins/hooks) protected when connected extensions change, so configured safeguards continue to apply. [PR #94545](https://github.com/openclaw/openclaw/pull/94545). Thanks @jesse-merhi.
|
||||
|
||||
#### Trusted package redirects
|
||||
|
||||
- Prevents authenticated package-source tokens from being sent to an allowed redirect on another origin, while the valid redirected download still completes. [b0df6dc](https://github.com/openclaw/openclaw/commit/b0df6dc10eb5b9e9fdca93063a16316f8589954e).
|
||||
|
||||
### Clients and Interfaces
|
||||
|
||||
#### Docker and Podman setup timeouts
|
||||
|
||||
- Prevents [Docker](https://docs.openclaw.ai/install/docker) and [Podman](https://docs.openclaw.ai/install/podman) setup from running unbounded on hosts where GNU timeout is installed as `gtimeout`, so image pulls, builds, and detached startup receive the intended guard. [62b2e9e](https://github.com/openclaw/openclaw/commit/62b2e9ef14b4be6fd396621c8e5e248331f08695).
|
||||
|
||||
### Plugins, Packaging, and QA
|
||||
|
||||
#### Codex service-tier clearing
|
||||
|
||||
- Prevents cleared [Codex service tiers](https://docs.openclaw.ai/tools/thinking) from being persisted as explicit stale state, so resumed or switched conversations use the normal default instead. [cd32d9f](https://github.com/openclaw/openclaw/commit/cd32d9ff91caf84c0ead38796ef096cdc5bea06e). Thanks @vincentkoc.
|
||||
|
||||
#### StepFun provider installation
|
||||
|
||||
- Restores [ClawHub discovery](https://docs.openclaw.ai/plugins/reference/stepfun) for the [StepFun provider](https://docs.openclaw.ai/providers/stepfun) plugin, so operators can install it through either ClawHub or npm. [ecb82f1](https://github.com/openclaw/openclaw/commit/ecb82f1be93024be23c1b191ebea92c63230b6c0). Thanks @vincentkoc.
|
||||
|
||||
### Docs and Operator Workflows
|
||||
|
||||
#### Doctor check ordering
|
||||
|
||||
- Keeps core [`openclaw doctor`](https://docs.openclaw.ai/gateway/doctor) diagnostics in their normal order before extension checks, making lint and repair output easier to follow. [PR #86627](https://github.com/openclaw/openclaw/pull/86627). Thanks @giodl73-repo.
|
||||
|
||||
## 2026.6.9
|
||||
|
||||
### Highlights
|
||||
@@ -132,7 +37,6 @@ This audited record covers the complete v2026.6.8..HEAD history: 423 merged PRs.
|
||||
|
||||
#### Pull requests
|
||||
|
||||
- **PR #92154** fix(qqbot): gate private group commands and close strict command visibility gaps. Thanks @sliverp.
|
||||
- **PR #90463** refactor: add session accessor seam with gateway consumer. Thanks @jalehman.
|
||||
- **PR #88656** Drop reasoning-only length turns from replay. Thanks @abel-zer0.
|
||||
- **PR #92856** feat(webui): add session workspace rail. Thanks @Solvely-Colin.
|
||||
|
||||
@@ -97,23 +97,6 @@ Welcome to the lobster tank! 🦞
|
||||
4. **Test/CI-only PRs for known `main` failures** → Don't open a PR. The Maintainer team is already tracking those failures, and PRs that only tweak tests or CI to chase them will be closed unless they are required to validate a new fix.
|
||||
5. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828)
|
||||
|
||||
## Issue, PR, and Contact Routing
|
||||
|
||||
Start from this routing map before creating GitHub items:
|
||||
|
||||
| Situation | Use | Required evidence |
|
||||
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| Product bug, regression, crash, or behavior defect | [Bug report](https://github.com/openclaw/openclaw/issues/new?template=bug_report.yml) | Repro steps, expected vs actual behavior, version, OS, model/provider route when relevant, logs/screenshots, impact |
|
||||
| Documentation bug or missing/contradictory docs | [Docs bug report](https://github.com/openclaw/openclaw/issues/new?template=docs_bug_report.yml) | Affected docs path or URL, verification steps, expected docs content, actual docs content, impact, evidence |
|
||||
| New feature, architecture change, or product improvement | [Feature request](https://github.com/openclaw/openclaw/issues/new?template=feature_request.yml) or Discord first | Problem, proposed solution, alternatives, impact, examples or prior art |
|
||||
| Onboarding, setup help, or general support question | Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828) | Do not open a GitHub issue unless there is a concrete product defect or docs gap |
|
||||
| Security vulnerability | See [Report a Vulnerability](#report-a-vulnerability) below | Do not file public issues for private security reports |
|
||||
| PR for an existing or newly filed issue | Use the [PR template](.github/pull_request_template.md) | Visible `Closes #<issue>` or `Related: #<issue>`, problem, shipped solution, user impact, validation evidence |
|
||||
|
||||
For agent-authored or otherwise non-trivial work, create or reuse the issue first, then open the PR against it. Bugs and very small fixes may go straight to PR, but still link existing context when it exists and fill out the PR template.
|
||||
|
||||
Do not guess who to tag. Let issue forms, labels/automation, `.github/CODEOWNERS`, and the maintainer areas above route the work. Mention a maintainer only when their listed area or owned path is directly relevant and you need a decision; otherwise rely on normal review. For coordinated change sets, ask in **#clawtributors** before opening more than the PR limit.
|
||||
|
||||
## PR Limits
|
||||
|
||||
We cap at **20 open PRs per author**. If you exceed this, the `r: too-many-prs` label is added and your PR is auto-closed. This is a hard limit.
|
||||
|
||||
@@ -304,9 +304,6 @@ by Peter Steinberger and the community.
|
||||
## Community
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
|
||||
Use the [issue chooser](https://github.com/openclaw/openclaw/issues/new/choose) for bugs, docs bugs, and feature requests;
|
||||
ask setup/support questions in [Discord](https://discord.gg/clawd); and report vulnerabilities through [SECURITY.md](SECURITY.md).
|
||||
PRs should link the relevant issue when possible and follow the [PR template](.github/pull_request_template.md) with problem, impact, and evidence.
|
||||
AI/vibe-coded PRs welcome! 🤖
|
||||
|
||||
Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and for
|
||||
|
||||
174
appcast.xml
174
appcast.xml
@@ -2,53 +2,6 @@
|
||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||
<channel>
|
||||
<title>OpenClaw</title>
|
||||
<item>
|
||||
<title>2026.6.10</title>
|
||||
<pubDate>Fri, 26 Jun 2026 23:37:36 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2606001090</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.6.10</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.6.10</h2>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li><strong>Automatic fast mode for talks:</strong> OpenClaw can enable fast mode for short conversational turns, then return to normal mode for longer runs with bounded fallback and delivery behavior. (#85104) Thanks @alexph-dev and @vincentkoc.</li>
|
||||
<li><strong>More reliable model routing:</strong> Zai model synthesis, GLM overload failover, and native reasoning-level selection now follow the active model catalog more consistently. (#94461, #93241, #94067, #94136) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @openperf, @civiltox, and @BorClaw.</li>
|
||||
<li><strong>Safer session and channel state:</strong> channel switches reset stale origin fields, and cron delivery awareness stays attached to the target session. (#95328, #93580) Thanks @ZengWen-DT, @jalehman, @gorkem2020, and @scotthuang.</li>
|
||||
<li><strong>Trusted policies survive hook composition:</strong> composed hook registries keep the trusted tool policies required by approval-sensitive flows. (#94545) Thanks @jesse-merhi.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li><strong>Agent and channel runtime:</strong> fast-mode state now survives retries, fallback transitions, progress events, and embedded/CLI/ACP normalization; session and channel routing retain the current target and delivery context. (#85104, #93580, #95328) Thanks @alexph-dev, @vincentkoc, @scotthuang, @ZengWen-DT, @jalehman, and @gorkem2020.</li>
|
||||
<li><strong>Provider behavior:</strong> model catalogs now supply the correct Zai base URL, overload classification, and native reasoning controls for live-discovered models. (#94461, #93241, #94067, #94136) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @openperf, @civiltox, and @BorClaw.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li><strong>Fast-mode and policy correctness:</strong> fallback cutoffs and reset notices are bounded, repeated progress events remain visible, Codex service-tier state is normalized, and trusted policies are not lost when hook registries are composed. (#85104, #94545) Thanks @alexph-dev, @vincentkoc, and @jesse-merhi.</li>
|
||||
<li><strong>Model and delivery edge cases:</strong> Zai and GLM failover paths use the right runtime metadata, while stale channel-origin state no longer leaks across session changes. (#94461, #93241, #95328) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @ZengWen-DT, @jalehman, and @gorkem2020.</li>
|
||||
<li><strong>Provider plugin onboarding:</strong> setup refreshes provider plugin registry metadata after installing setup-selected provider plugins, so auth continuation uses the newly installed provider instead of stale registry state. (#95792) Thanks @snowzlmbot.</li>
|
||||
</ul>
|
||||
<h3>Complete contribution record</h3>
|
||||
This audited record covers the complete v2026.6.9..HEAD history: 12 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
|
||||
<h4>Pull requests</h4>
|
||||
<ul>
|
||||
<li><strong>PR #86627</strong> Keep core doctor health in contribution order. Thanks @giodl73-repo.</li>
|
||||
<li><strong>PR #93580</strong> fix: preserve cron delivery awareness for target sessions. Thanks @scotthuang and @jalehman.</li>
|
||||
<li><strong>PR #95030</strong> refactor: add SDK transcript identity target API. Thanks @jalehman.</li>
|
||||
<li><strong>PR #94838</strong> refactor(copilot): complete harness lifecycle parity. Thanks @vincentkoc.</li>
|
||||
<li><strong>PR #95328</strong> fix(sessions): reset stale per-channel origin fields on channel switch. Related #95325. Thanks @ZengWen-DT and @jalehman and @gorkem2020.</li>
|
||||
<li><strong>PR #94461</strong> fix(zai): fall back to manifest baseUrl for synthesized GLM-5 models. Related #94269. Thanks @Pandah97 and @chrysb.</li>
|
||||
<li><strong>PR #93241</strong> fix(agents): classify Zhipu GLM overload as overloaded for failover. Related #93211. Thanks @0xghost42 and @zhengli0922.</li>
|
||||
<li><strong>PR #94067</strong> fix(channels): resolve native /think menu levels via runtime catalog for live-discovered models. Related #93835. Thanks @openperf and @civiltox.</li>
|
||||
<li><strong>PR #94136</strong> fix(zai): expose GLM-5.2 reasoning levels [AI-assisted]. Thanks @BorClaw.</li>
|
||||
<li><strong>PR #85104</strong> feat: fast talks auto mode. Related #85087. Thanks @alexph-dev.</li>
|
||||
<li><strong>PR #94545</strong> fix: keep trusted policies with hook registry. Thanks @jesse-merhi.</li>
|
||||
<li><strong>PR #95792</strong> fix(onboard): refresh provider plugin registry after setup installs. Related #95765. Thanks @snowzlmbot.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.6.10/OpenClaw-2026.6.10.zip" length="56115790" type="application/octet-stream" sparkle:edSignature="MEeGG8+WePhUg9uDShznmdhhAgy/WWe7bAwr4XRTauNdrM441iziQYIlwhfNrtHDHX+uE1/tkRtIMcELfuekAg=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.6.8</title>
|
||||
<pubDate>Tue, 16 Jun 2026 17:17:20 +0000</pubDate>
|
||||
@@ -171,5 +124,132 @@ This audited record covers the complete v2026.6.9..HEAD history: 12 merged PRs.
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.6.5/OpenClaw-2026.6.5.zip" length="55725877" type="application/octet-stream" sparkle:edSignature="EKr7gCfpEVStis9HSADJk1CWYbmH2MHMqSgNfZvLbBFCBWmk3pjBJS6K2qkxkq5lIbTj4H+Lo7Iri6ip/xTGDA=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.6.1</title>
|
||||
<pubDate>Wed, 03 Jun 2026 21:26:22 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026060190</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.6.1</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.6.1</h2>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>Agents and CLI-backed runtimes recover more cleanly from interrupted tool calls, stale session bindings, compaction handoffs, and media delivery retries. (#88129, #88136, #88141, #88162, #88182)</li>
|
||||
<li>Channels and mobile delivery are steadier across Telegram, WhatsApp, iMessage, Slack, Discord, Microsoft Teams, Google Chat, Google Meet, and iOS realtime Talk. (#88096, #88105, #88183, #88231)</li>
|
||||
<li>Provider and plugin requests now bound more timers, retries, OAuth/device-code lifetimes, media downloads, local service probes, and generated-content polling paths before they can hang a run.</li>
|
||||
<li>Skills, session metadata, gateway runtime state, plugin metadata, memory watchers, and store writes do less repeated work on hot paths while keeping config, dispatch, and Linux file-watch behavior stable. (#89185, #89188, #85351) Thanks @RomneyDa and @NianJiuZst.</li>
|
||||
<li>Skills and plugin loading now handle stale disabled snapshots and loader failures more clearly, so channel turns avoid disabled SecretRefs and operators get better recovery guidance. (#79072, #79173) Thanks @zeus1959.</li>
|
||||
<li>Workboard, SecretRef plugin manifests, hosted iOS push relay, and external Copilot/Tokenjuice packaging add broader orchestration, integration, and plugin delivery surfaces. (#82326, #87469, #87796, #88107, #88117)</li>
|
||||
<li>Skill Workshop now has a fuller Control UI flow with proposal lists, today actions, revision handoff, searchable file previews, review states, locale coverage, and reusable session routing.</li>
|
||||
<li>Chat and Control UI startup paths keep sends alive through history loading, stream deltas incrementally, skip markdown work while streaming, keep drafts local while typing, clear the composer after sends, trace first-output latency, prioritize first connect, and expose calmer composer controls. (#88772, #88825, #88998, #89030, #89106) Thanks @vincentkoc and @sallyom.</li>
|
||||
<li>Provider coverage and model metadata now include MiniMax M3, account OAuth endpoints, Google/Vertex catalog fixes, OpenRouter SQLite model caching, Copilot Claude 1M capabilities, Foundry reasoning alignment, and OpenAI response replay guards. (#88480, #88512, #88851, #88860)</li>
|
||||
<li>iMessage monitor state, inbound queues, and plugin install ledgers moved toward SQLite-backed state so restarts and local monitors recover with less duplicate filesystem scanning. (#88794, #88797)</li>
|
||||
<li>Release, CI, Docker, E2E, plugin install, and diagnostics lanes now cap more logs, response bodies, readiness probes, artifact checks, status polling, child workflow waits, docker package cleanup, quiet test stalls, and rollback snapshots so failures report bounded proof instead of stalling. (#88966) Thanks @RomneyDa.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Docs: add a dedicated Skill Workshop guide covering governed skill creation, reviewable proposals, CLI, Gateway, agent tool behavior, approval policy, support files, and recovery, and refresh the ClawHub showcase cards. (#88734) Thanks @shakkernerd and @vyctorbrzezowski.</li>
|
||||
<li>Skills: let the <code>skill_workshop</code> agent tool apply, reject, and quarantine explicit proposals through the guarded review flow. Thanks @shakkernerd.</li>
|
||||
<li>Skills: let proposals carry approved support files under standard skill folders, with scanner, hash, and rollback safeguards. Thanks @shakkernerd.</li>
|
||||
<li>Skills: let pending proposals be revised in place with versioned, dated proposal frontmatter before approval. Thanks @shakkernerd.</li>
|
||||
<li>Skills: add Skill Workshop with pending proposals, CLI/Gateway review actions, rollback metadata, and the <code>skill_workshop</code> agent tool. Thanks @shakkernerd.</li>
|
||||
<li>Skill Workshop: add the Control UI navigation, styled dashboard, proposal today view, revision dialog, file preview modal, searchable preview files, reusable session handoff, and localized strings.</li>
|
||||
<li>Plugins: externalize Tokenjuice as the official <code>@openclaw/tokenjuice</code> plugin with npm and ClawHub publish metadata.</li>
|
||||
<li>Plugins: externalize the GitHub Copilot agent runtime as the official <code>@openclaw/copilot</code> plugin with npm and ClawHub publish metadata.</li>
|
||||
<li>iOS: add hosted push relay defaults, realtime Talk playback, and a guarded WebSocket ping path for more reliable mobile sessions. (#88096, #88105, #88231)</li>
|
||||
<li>iOS: support native iPad display layouts.</li>
|
||||
<li>Workboard: add orchestration primitives and agent coordination tools for multi-agent planning and run tracking. (#87469)</li>
|
||||
<li>Workboard: wire task-backed board runs and show task comments in the edit modal.</li>
|
||||
<li>Code mode: add internal namespaces for scoped agent/global sessions and exact namespace tool dispatch. (#88043)</li>
|
||||
<li>Code mode: add MCP API files and docs for code-mode integrations.</li>
|
||||
<li>Control UI: add a Dreaming-tab agent selector and propagate the selected agent through Dreaming status, diary, and diary actions. (#78748) Thanks @stevenepalmer.</li>
|
||||
<li>Control UI: add calmer chat composer controls, local draft typing state, and first-output latency instrumentation for active chat entry. (#88772, #88998) Thanks @vincentkoc.</li>
|
||||
<li>Plugins: add a SecretRef provider integration manifest contract and extract shared LLM core packages for provider/plugin reuse. (#82326, #88117)</li>
|
||||
<li>Plugins: persist the plugin install index in SQLite so installed package lookup survives reloads with less filesystem scanning. (#88794)</li>
|
||||
<li>Providers: add MiniMax M3 model support. (#88860)</li>
|
||||
<li>Doctor: add disk space health checks and stabilize post-upgrade JSON probes.</li>
|
||||
<li>Channels: store inbound queues in SQLite and migrate iMessage monitor state to SQLite-backed tracking. (#88797)</li>
|
||||
<li>Skills: add the core skills index and centralize skills runtime loading, status, filtering, and prompt formatting.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Release/CI/E2E: fail early when Crabbox sparse-sync full checkouts do not have enough local disk, with guidance for moving the sync root.</li>
|
||||
<li>Build: render independent CLI startup metadata help snapshots concurrently to cut cold build-all metadata time.</li>
|
||||
<li>Plugins: stop timed-out package-boundary prep steps by process group so descendant TypeScript/helper processes do not survive local check cleanup.</li>
|
||||
<li>Control UI: serve static assets asynchronously after safe-open checks so large UI files do not block Gateway request handling.</li>
|
||||
<li>Scripts/UI: forward direct wrapper SIGHUP shutdown to child processes so terminal hangups do not leave wrapped dev commands running.</li>
|
||||
<li>Gateway: return the post-expiration pending-work revision from node drains so reconnecting nodes do not observe stale queue revisions after expired items are pruned.</li>
|
||||
<li>Release/CI/E2E: keep temporary full-sync checkouts alive while slow Crabbox leases boot, so sparse worktree runs do not lose their sync source before file-list generation.</li>
|
||||
<li>Release/CI/E2E: normalize inherited Linux <code>C.UTF-8</code> locale settings before raw AWS macOS Crabbox bootstrap commands, avoiding macOS locale warnings during package-manager hydration.</li>
|
||||
<li>Release/CI/E2E: keep gateway watch regression checks from copying large static plugin assets inside the measured idle window.</li>
|
||||
<li>Update: keep core updates nonblocking when a missing external plugin repair download stalls, while still blocking installed active plugin payload smoke failures.</li>
|
||||
<li>Agents/providers: keep streaming tool-call argument parsing record-shaped when providers emit valid non-object JSON such as <code>null</code> or arrays.</li>
|
||||
<li>Release/CI/E2E: reset incremental log readers when watched log files rotate without shrinking, so same-size replacements do not hide new readiness or RPC lines.</li>
|
||||
<li>Talk: preserve explicit <code>null</code> payloads on controller-created turn and output-audio lifecycle events.</li>
|
||||
<li>Agents/TUI: keep local custom provider runs from loading plugin runtime and auth alias metadata when plugins are disabled.</li>
|
||||
<li>Agents/TUI: restore in-flight TUI run switch-back behavior, keep no-policy native hook fallback available, guard vanished workspaces, and keep lightweight isolated subagents lightweight.</li>
|
||||
<li>Agents/media: keep async image, music, and video generation starts from ending the Codex turn, so mixed requests can continue with summaries or other work while media renders in the background.</li>
|
||||
<li>Agents/Codex: keep public OpenAI API-key profiles from being treated as native Codex app-server auth while preserving persisted Codex OAuth sessions.</li>
|
||||
<li>Agents/Codex: stream Codex app-server final-answer partials to live reply previews, preserve ACP metadata in SQLite, prefer real tool results over synthetic repair output, prevent aborted app-server turn handles from lingering, migrate legacy OpenAI Codex <code>lastGood</code> auth state, and preserve workspace/session metadata through ACP runtime refactors. (#88405, #88724, #88730) Thanks @vincentkoc.</li>
|
||||
<li>Control UI: keep collapsed tool cards labeled with the tool name and action instead of generic output text. Thanks @shakkernerd.</li>
|
||||
<li>Agents/Codex: surface Skill Workshop guidance in Codex app-server prompts when <code>skill_workshop</code> is available. Thanks @shakkernerd.</li>
|
||||
<li>Skill Workshop: restore and localize the Control UI board/today view switcher so review workflows keep their intended layout toggle across locales. Thanks @shakkernerd.</li>
|
||||
<li>Agents/auth: write auth profiles atomically, dispatch auth failures by type, add force re-login recovery, preserve workspaces during state-only uninstall, and compact before oversized turns so recovery paths avoid partial state. (#89181) Thanks @RomneyDa.</li>
|
||||
<li>Skills: skip disabled skill env overrides from stale persisted snapshots so disabled skill <code>apiKey</code> SecretRefs cannot abort embedded or channel turns. (#79072, #79173) Thanks @zeus1959.</li>
|
||||
<li>Skill Workshop: render the Control UI tab from filtered navigation state and keep filtered fallback routing stable.</li>
|
||||
<li>CLI: avoid live catalog validation during <code>openclaw agents add</code>, so adding a secondary agent no longer depends on provider catalog availability. (#76284, #88314) Thanks @zhangguiping-xydt.</li>
|
||||
<li>CLI: keep <code>plugins list --json</code> on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph.</li>
|
||||
<li>CLI/desktop: bridge WSL clipboard operations through the shell, recognize manual-update launchd jobs, and keep machine-readable startup output parseable during progress setup. (#88764, #88689) Thanks @alexzhu0.</li>
|
||||
<li>Plugins: make PixVerse external-plugin ClawHub metadata explicit and keep it out of bundled dist builds.</li>
|
||||
<li>Plugins: clarify plugin loader failure guidance so missing or incompatible plugin packages point operators at the right repair path.</li>
|
||||
<li>Plugins: preserve npm plugin roots after blocked installs, skip plugin-local <code>openclaw</code> peer symlinks during rollback snapshots, relink those peers after restore, isolate cached tool runtime siblings, and isolate web-provider factory failures so one bad plugin does not poison sibling runtime paths. (#77237, #88807)</li>
|
||||
<li>Cron: keep SQLite cron migrations compatible with legacy run-log tables, archived job stores, diagnostic cron names, and legacy one-shot delete-after-run behavior. (#88285)</li>
|
||||
<li>Cron: keep update delivery validation scoped, harden restart state, and retire MCP runtimes on isolated cron cleanup.</li>
|
||||
<li>Memory: serialize QMD update/embed writes per store, reduce Linux watcher fan-out, retry transient FileProvider-backed reads, preserve phase signals on read errors, harden envelope metadata sanitization, reattach Linux native watchers when directories are recreated, and rewrite generated transcript paths on rollover so memory/search state survives concurrent gateway and CLI activity. (#66339, #85931, #89185, #89188, #85351) Thanks @openperf, @amittell, @RomneyDa, and @NianJiuZst.</li>
|
||||
<li>Memory: keep vector-disabled FTS indexes from resolving embedding providers during sync and search.</li>
|
||||
<li>Providers: bound generated media downloads from OpenAI, Runway, xAI, MiniMax, BytePlus, DashScope-compatible, FAL, OpenRouter, Google, Vydra, and Comfy providers.</li>
|
||||
<li>Providers: resolve Google defaults to <code>google-generative-ai</code>, register Vertex static catalog rows, align Foundry reasoning metadata, skip DeepSeek V4 thinking params on Foundry fallback, use MiniMax account OAuth endpoints, preserve Copilot Claude 1M capabilities, suppress disabled Ollama reasoning output, forward Gemini stop sequences, strip Kimi-incompatible Anthropic cache markers, keep OpenAI stop-finished tool calls, and avoid replay ids when the Responses store is disabled. (#88480, #88512, #76612) Thanks @coder999999999, @BryanTegomoh, and @vliuyt.</li>
|
||||
<li>Providers: cap GitHub Copilot OAuth request timeouts before creating abort signals.</li>
|
||||
<li>Cron: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot.</li>
|
||||
<li>Agents/Codex: keep live session locks during cleanup, recover interrupted CLI tool transcripts, preserve Codex auth and compaction session identity, clear orphan tool state, cap app-server idle timers, and keep media completion delivery retryable. (#88129, #88136, #88141, #88162, #88182)</li>
|
||||
<li>Chat/UI: show Gateway chat failures as visible assistant messages in the Control UI instead of only setting an invisible error state.</li>
|
||||
<li>Channels: cap Telegram, Discord, WhatsApp, Signal, Feishu, Google Chat, Microsoft Teams, QQBot, Nostr, Zalo, Zalouser, and Nextcloud-style request/retry timers; preserve SMS approval reply routes; and retry WhatsApp QR login 408 timeouts. (#88183)</li>
|
||||
<li>Security/config parsing: reject unsafe OAuth/token lifetimes, retry-after delays, inbound timestamps, response body sizes, command timeout config, sandbox observer token TTLs, and gateway WebSocket calls after close.</li>
|
||||
<li>Providers/media: cap local service, model, usage, queue, generated media, TTS, music, workflow polling, and provider OAuth request timers across hosted and local providers.</li>
|
||||
<li>Release/CI/E2E: bound release candidate reads, beta smoke REST calls, plugin npm verification commands, changelog restore, cross-OS process groups, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Telegram credential timeouts, Control UI i18n and CLI startup metadata generation, Vitest routing, dependency guard admin approvals, child workflow failure detection, quiet Node test shard stalls, docker package cleanup, and mainline test flakes. (#88127, #88137, #88155, #88160, #88966) Thanks @RomneyDa.</li>
|
||||
<li>Release/CI/E2E: keep Kitchen Sink live plugin MCP probes resolving source-checkout workspace packages and align the live gauntlet with current Kitchen Sink diagnostics.</li>
|
||||
<li>Release/CI/E2E: run the secret-provider integration proof through the repo pnpm runner so native macOS and Windows validation use the hydrated package-manager shim.</li>
|
||||
<li>Release/CI/E2E: run the Telegram desktop proof gateway through the repo pnpm runner so native macOS proof uses the hydrated package-manager shim.</li>
|
||||
<li>Docs/CI: run Mintlify anchor checks through the repo pnpm runner so docs link validation works when pnpm is only available through the hydrated package-manager shim.</li>
|
||||
<li>Agents: keep configured fallback model metadata typed so provider params, context-token caps, and media input limits do not break changed-gate typechecks.</li>
|
||||
<li>Agents: accept hidden <code>sessions_send</code> body aliases before validation while keeping the model-facing <code>message</code> schema canonical. (#88229) Thanks @zhangguiping-xydt.</li>
|
||||
<li>Chat/UI: preserve startup chat sends during history loading, unblock the initial Control UI chat send, stream chat deltas incrementally, skip markdown parsing while streaming, keep drafts local while typing, guard composer rerenders, honor Chromium executable overrides, and detect system Chromium for E2E. (#88998) Thanks @vincentkoc.</li>
|
||||
<li>Channels: stop schema-padded poll modifiers from turning normal <code>send</code> actions into invalid poll sends. (#89601) Thanks @codezz.</li>
|
||||
<li>Channels: preserve long Feishu streaming replies, send visible fallbacks when accepted Feishu turns produce no final reply, tolerate iMessage self-chat timestamp skew, preserve colon-prefixed slash commands in mention parsing, decode Nostr <code>npub</code> allowlists correctly, and suppress raw provider errors during channel delivery. (#87896)</li>
|
||||
<li>Config/status/doctor: skip unresolved shell references in state-dir dotenv files, resolve gateway auth secrets during deep status audits, respect explicit PI runtime policy, report runtime tool-schema errors, and keep post-upgrade JSON stable. (#88288)</li>
|
||||
<li>Gateway/session state: list commands from the Gateway plugin registry, harden MCP loopback tool schemas, hide phantom agent-store rows from <code>sessions.list</code>, make task persistence failures explicit, and carry session UUIDs on interactive dispatch events.</li>
|
||||
<li>Gateway/plugins: narrow plugin lookup memoization to the stable plugin/runtime inputs, avoiding repeated lookup work without mixing disabled or filtered plugin state.</li>
|
||||
<li>OpenAI/TTS: handle speed directives for OpenAI TTS voices. (#74089)</li>
|
||||
<li>CI/Crabbox: keep default runner capacity on the Azure credit-backed on-demand D4 lane with the Azure SSH port and a Git-independent full check job, so broad validation avoids low-priority spot quota stalls, hydrate port mismatches, non-Git hydrated workspaces, and stale AWS region hints.</li>
|
||||
<li>CI/Crabbox: route Crabbox wrapper and Testbox workflow edits to their regression tests so changed-test gates do not silently run zero specs.</li>
|
||||
<li>CI/workflows: route workflow sanity helper edits to their guard tests and cover composite-action input interpolation checks.</li>
|
||||
<li>CI/tooling: route CI scope, dependency, changelog, and docs helper edits to their owner tests instead of silently skipping changed-test coverage.</li>
|
||||
<li>CI/tooling: route package, release, and install helper edits to their owner tests so changed-test gates cover publish and installer script changes.</li>
|
||||
<li>CI/tooling: route shared script library edits through their owner tests so lock, process, safety, and scan helpers do not skip changed-test coverage.</li>
|
||||
<li>CI/tooling: skip expensive import-graph scans once a changed diff already requires broad fallback, keeping local changed-test planning fast while still collecting explicit owner tests.</li>
|
||||
<li>CI/tooling: route script edits through conventional owner tests when matching <code>test/scripts</code> or <code>src/scripts</code> coverage already exists.</li>
|
||||
<li>CI/tooling: honor option terminators in the memory FD repro script so follow-on arguments are not reparsed.</li>
|
||||
<li>Release/CI/E2E: assert plugin lifecycle runtime inspect output instead of only capturing it.</li>
|
||||
<li>Release/CI/E2E: make gateway-network prove the advertised health RPC and retry early WebSocket closes without burning full open timeouts.</li>
|
||||
<li>Release/CI/E2E: honor option terminators across release, Parallels smoke, plugin gauntlet, and extension-memory scripts.</li>
|
||||
<li>Release/CI/E2E: fail plugin gateway gauntlet QA chunks when the requested suite summary is missing or invalid.</li>
|
||||
<li>Performance: prebuild QA runtime probes with generated plugin assets but without CLI startup metadata.</li>
|
||||
<li>Performance: skip declaration bundling for runtime-only CLI startup and gateway watch build profiles.</li>
|
||||
<li>Performance: reuse prepared provider handles, strict tool schemas, gateway runtime metadata, session maintenance config, plugin metadata, bundled skill allowlists, package-local plugin artifacts, single-entry store writes, and validated/serialized session prompt blobs.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.6.1/OpenClaw-2026.6.1.zip" length="55062100" type="application/octet-stream" sparkle:edSignature="PVp8E2HBCvikB/0LCr36lFEyHPAzoFA2ScT6LW27FlzvP+m4r1AEuVN2UrtgWlpkGSsn4Eav0kPJe32u4ObNBw=="/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,5 +2,5 @@
|
||||
# Source of truth: apps/android/version.json
|
||||
# Generated by scripts/android-sync-versioning.ts.
|
||||
|
||||
OPENCLAW_ANDROID_VERSION_NAME=2026.6.10
|
||||
OPENCLAW_ANDROID_VERSION_CODE=2026061001
|
||||
OPENCLAW_ANDROID_VERSION_NAME=2026.6.9
|
||||
OPENCLAW_ANDROID_VERSION_CODE=2026060901
|
||||
|
||||
@@ -56,38 +56,6 @@ Recommended workflow:
|
||||
|
||||
The third-party flavor is archived as a signed APK for non-Play distribution. It is not uploaded by the Play release lane.
|
||||
|
||||
## Release SHA tracking
|
||||
|
||||
Successful Play build uploads create a non-tag Git ref that records the source
|
||||
commit for the uploaded store build:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/android/<versionName>-<versionCode>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/android/2026.6.10-2026061008
|
||||
```
|
||||
|
||||
These refs are intentionally outside `refs/tags/*` and `refs/heads/*`. They do
|
||||
not appear on GitHub release or tag pages, and they do not participate in the
|
||||
core OpenClaw release machinery.
|
||||
|
||||
`pnpm android:release:upload` checks the ref before uploading the Play build and
|
||||
records it only after `upload_to_play_store` succeeds. Existing refs are
|
||||
immutable: the same ref at the same SHA is accepted, while the same ref at a
|
||||
different SHA fails. `GOOGLE_PLAY_VALIDATE_ONLY=1` still checks the ref but does
|
||||
not record it because no Play build is published.
|
||||
|
||||
Useful direct commands:
|
||||
|
||||
```bash
|
||||
pnpm mobile:release:preflight -- --platform android --version 2026.6.10 --version-code 2026061008
|
||||
pnpm mobile:release:resolve -- --platform android --version 2026.6.10 --version-code 2026061008
|
||||
```
|
||||
|
||||
## Signing model
|
||||
|
||||
`apps/android/Config/ReleaseSigning.json` pins the Android signing assets in the shared private `apps-signing` repo. The Android pipeline uses the same `MATCH_PASSWORD` release-owner secret as iOS, but the Android files are managed by `scripts/android-release-signing.mjs` instead of Fastlane `match`.
|
||||
|
||||
@@ -2,7 +2,6 @@ package ai.openclaw.app.ui
|
||||
|
||||
import ai.openclaw.app.GatewayConnectionProblem
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.R
|
||||
import ai.openclaw.app.ui.mobileCardSurface
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
@@ -52,7 +51,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
@@ -102,7 +100,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { viewModel.declineGatewayTrustPrompt() },
|
||||
containerColor = mobileCardSurface,
|
||||
title = { Text(stringResource(R.string.trust_this_gateway), style = mobileHeadline, color = mobileText) },
|
||||
title = { Text("Trust this gateway?", style = mobileHeadline, color = mobileText) },
|
||||
text = {
|
||||
val message =
|
||||
if (prompt.previousFingerprintSha256.isNullOrBlank()) {
|
||||
@@ -121,7 +119,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
onClick = { viewModel.acceptGatewayTrustPrompt() },
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = mobileAccent),
|
||||
) {
|
||||
Text(stringResource(R.string.trust_and_continue))
|
||||
Text("Trust and continue")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
@@ -129,7 +127,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
onClick = { viewModel.declineGatewayTrustPrompt() },
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = mobileTextSecondary),
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
Text("Cancel")
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -160,10 +158,9 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Text(stringResource(R.string.gateway_connection), style = mobileTitle1, color = mobileText)
|
||||
Text("Gateway Connection", style = mobileTitle1, color = mobileText)
|
||||
Text(
|
||||
if (isConnected) stringResource(R.string.connected_gateway_ready)
|
||||
else stringResource(R.string.connect_gateway_get_started),
|
||||
if (isConnected) "Your gateway is active and ready." else "Connect to your gateway to get started.",
|
||||
style = mobileCallout,
|
||||
color = mobileTextSecondary,
|
||||
)
|
||||
@@ -194,7 +191,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(stringResource(R.string.endpoint), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text("Endpoint", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text(activeEndpoint, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText)
|
||||
}
|
||||
}
|
||||
@@ -216,7 +213,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(stringResource(R.string.status), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text("Status", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text(statusText, style = mobileBody, color = if (isConnected) mobileSuccess else mobileText)
|
||||
}
|
||||
}
|
||||
@@ -241,7 +238,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
) {
|
||||
Icon(Icons.Default.PowerSettingsNew, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.disconnect), style = mobileHeadline.copy(fontWeight = FontWeight.SemiBold))
|
||||
Text("Disconnect", style = mobileHeadline.copy(fontWeight = FontWeight.SemiBold))
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
@@ -310,7 +307,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
contentColor = Color.White,
|
||||
),
|
||||
) {
|
||||
Text(stringResource(R.string.connect_gateway), style = mobileHeadline.copy(fontWeight = FontWeight.Bold))
|
||||
Text("Connect Gateway", style = mobileHeadline.copy(fontWeight = FontWeight.Bold))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +354,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
) {
|
||||
Icon(Icons.Default.ContentCopy, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.copy_report_for_claw), style = mobileCallout.copy(fontWeight = FontWeight.Bold))
|
||||
Text("Copy Report for Claw", style = mobileCallout.copy(fontWeight = FontWeight.Bold))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,7 +373,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(stringResource(R.string.advanced_controls), style = mobileHeadline, color = mobileText)
|
||||
Text("Advanced controls", style = mobileHeadline, color = mobileText)
|
||||
Text("Setup code, endpoint, TLS, token, password, onboarding.", style = mobileCaption1, color = mobileTextSecondary)
|
||||
}
|
||||
Icon(
|
||||
@@ -398,15 +395,15 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 14.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
Text(stringResource(R.string.connection_method), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text("Connection method", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
MethodChip(
|
||||
label = stringResource(R.string.setup_code),
|
||||
label = "Setup Code",
|
||||
active = inputMode == ConnectInputMode.SetupCode,
|
||||
onClick = { inputMode = ConnectInputMode.SetupCode },
|
||||
)
|
||||
MethodChip(
|
||||
label = stringResource(R.string.manual),
|
||||
label = "Manual",
|
||||
active = inputMode == ConnectInputMode.Manual,
|
||||
onClick = { inputMode = ConnectInputMode.Manual },
|
||||
)
|
||||
@@ -422,14 +419,14 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
)
|
||||
|
||||
if (inputMode == ConnectInputMode.SetupCode) {
|
||||
Text(stringResource(R.string.setup_code), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text("Setup Code", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
OutlinedTextField(
|
||||
value = setupCode,
|
||||
onValueChange = {
|
||||
setupCode = it
|
||||
validationText = null
|
||||
},
|
||||
placeholder = { Text(stringResource(R.string.paste_setup_code), style = mobileBody, color = mobileTextTertiary) },
|
||||
placeholder = { Text("Paste setup code", style = mobileBody, color = mobileTextTertiary) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
minLines = 3,
|
||||
maxLines = 5,
|
||||
@@ -463,7 +460,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
)
|
||||
}
|
||||
|
||||
Text(stringResource(R.string.host), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text("Host", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
OutlinedTextField(
|
||||
value = manualHostInput,
|
||||
onValueChange = {
|
||||
@@ -505,7 +502,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(stringResource(R.string.use_tls), style = mobileHeadline, color = mobileText)
|
||||
Text("Use TLS", style = mobileHeadline, color = mobileText)
|
||||
Text(
|
||||
"Turn this on for Tailscale or public hosts. Private LAN ws:// remains supported.",
|
||||
style = mobileCallout,
|
||||
@@ -528,7 +525,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
)
|
||||
}
|
||||
|
||||
Text(stringResource(R.string.token_optional), style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
Text("Token (optional)", style = mobileCaption1.copy(fontWeight = FontWeight.SemiBold), color = mobileTextSecondary)
|
||||
OutlinedTextField(
|
||||
value = gatewayToken,
|
||||
onValueChange = { viewModel.setGatewayToken(it) },
|
||||
@@ -549,7 +546,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
OutlinedTextField(
|
||||
value = passwordInput,
|
||||
onValueChange = { passwordInput = it },
|
||||
placeholder = { Text(stringResource(R.string.password), style = mobileBody, color = mobileTextTertiary) },
|
||||
placeholder = { Text("password", style = mobileBody, color = mobileTextTertiary) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii),
|
||||
@@ -566,7 +563,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
HorizontalDivider(color = mobileBorder)
|
||||
|
||||
TextButton(onClick = { viewModel.setOnboardingCompleted(false) }) {
|
||||
Text(stringResource(R.string.run_onboarding_again), style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), color = mobileAccent)
|
||||
Text("Run onboarding again", style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), color = mobileAccent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,6 @@ import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -213,13 +212,7 @@ fun OnboardingFlow(
|
||||
AlertDialog(
|
||||
onDismissRequest = viewModel::declineGatewayTrustPrompt,
|
||||
containerColor = ClawTheme.colors.surfaceRaised,
|
||||
title = {
|
||||
Text(
|
||||
stringResource(R.string.trust_this_gateway),
|
||||
style = ClawTheme.type.section,
|
||||
color = ClawTheme.colors.text,
|
||||
)
|
||||
},
|
||||
title = { Text("Trust this gateway?", style = ClawTheme.type.section, color = ClawTheme.colors.text) },
|
||||
text = {
|
||||
Text(
|
||||
"Verify the certificate fingerprint before continuing.\n\n${prompt.fingerprintSha256}",
|
||||
@@ -229,12 +222,12 @@ fun OnboardingFlow(
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = viewModel::acceptGatewayTrustPrompt) {
|
||||
Text(stringResource(R.string.trust_and_continue))
|
||||
Text("Trust")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = viewModel::declineGatewayTrustPrompt) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
Text("Cancel")
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -541,24 +534,20 @@ private fun GatewaySetupScreen(
|
||||
Column(modifier = Modifier.fillMaxSize().imePadding(), verticalArrangement = Arrangement.SpaceBetween) {
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(9.dp)) {
|
||||
item {
|
||||
OnboardingHeader(
|
||||
title = stringResource(R.string.gateway_setup),
|
||||
subtitle = stringResource(R.string.connect_to_gateway),
|
||||
onBack = onBack,
|
||||
)
|
||||
OnboardingHeader(title = "Gateway Setup", subtitle = "Connect to your Gateway", onBack = onBack)
|
||||
}
|
||||
item {
|
||||
GatewayOption(
|
||||
icon = Icons.Default.QrCode2,
|
||||
title = stringResource(R.string.scan_setup_code),
|
||||
subtitle = stringResource(R.string.use_gateway_qr),
|
||||
title = "Scan setup code",
|
||||
subtitle = "Use your Gateway QR or setup code",
|
||||
onClick = onScan,
|
||||
)
|
||||
}
|
||||
item {
|
||||
GatewayOption(
|
||||
icon = Icons.Default.WifiTethering,
|
||||
title = stringResource(R.string.nearby_gateway),
|
||||
title = "Nearby gateway",
|
||||
subtitle = nearbyGateway.subtitle,
|
||||
status = nearbyGateway.status,
|
||||
onClick = onUseNearby.takeIf { nearbyGateway.canConnect },
|
||||
@@ -567,8 +556,8 @@ private fun GatewaySetupScreen(
|
||||
item {
|
||||
GatewayOption(
|
||||
icon = Icons.Default.Link,
|
||||
title = stringResource(R.string.enter_gateway_url),
|
||||
subtitle = stringResource(R.string.connect_manual_url),
|
||||
title = "Enter gateway URL",
|
||||
subtitle = "Connect using a manual URL",
|
||||
onClick = { advancedOpen = true },
|
||||
)
|
||||
}
|
||||
@@ -649,7 +638,7 @@ private fun GatewayRecoveryScreen(
|
||||
|
||||
ClawScaffold(modifier = modifier, contentPadding = PaddingValues(horizontal = 18.dp, vertical = 16.dp)) {
|
||||
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(18.dp)) {
|
||||
OnboardingHeader(title = stringResource(R.string.gateway_setup), onBack = onBack)
|
||||
OnboardingHeader(title = "Gateway Recovery", onBack = onBack)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Icon(
|
||||
@@ -934,9 +923,7 @@ private fun PermissionTopBar(onBack: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showHelp = false },
|
||||
containerColor = ClawTheme.colors.surfaceRaised,
|
||||
title = {
|
||||
Text(stringResource(R.string.permissions), style = ClawTheme.type.section, color = ClawTheme.colors.text)
|
||||
},
|
||||
title = { Text("Permissions", style = ClawTheme.type.section, color = ClawTheme.colors.text) },
|
||||
text = {
|
||||
Text(
|
||||
"Choose what this phone can share with OpenClaw. You can change these later in Settings.",
|
||||
@@ -946,7 +933,7 @@ private fun PermissionTopBar(onBack: () -> Unit) {
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showHelp = false }) {
|
||||
Text(stringResource(R.string.done))
|
||||
Text("Done")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">اتصال البوابة</string>
|
||||
<string name="connect_gateway">توصيل البوابة</string>
|
||||
<string name="disconnect">قطع الاتصال</string>
|
||||
<string name="trust_this_gateway">هل تثق بهذه البوابة؟</string>
|
||||
<string name="trust_and_continue">الثقة والمتابعة</string>
|
||||
<string name="cancel">إلغاء</string>
|
||||
<string name="endpoint">نقطة النهاية</string>
|
||||
<string name="status">الحالة</string>
|
||||
<string name="connected_gateway_ready">بوابتك نشطة وجاهزة.</string>
|
||||
<string name="connect_gateway_get_started">اتصل ببوابتك للبدء.</string>
|
||||
<string name="copy_report_for_claw">نسخ التقرير لـ Claw</string>
|
||||
<string name="advanced_controls">عناصر التحكم المتقدمة</string>
|
||||
<string name="connection_method">طريقة الاتصال</string>
|
||||
<string name="setup_code">رمز الإعداد</string>
|
||||
<string name="manual">يدوي</string>
|
||||
<string name="paste_setup_code">الصق رمز الإعداد</string>
|
||||
<string name="host">المضيف</string>
|
||||
<string name="use_tls">استخدام TLS</string>
|
||||
<string name="token_optional">الرمز المميز (اختياري)</string>
|
||||
<string name="password">كلمة المرور</string>
|
||||
<string name="run_onboarding_again">تشغيل الإعداد الأولي مرة أخرى</string>
|
||||
<string name="resolved_endpoint">نقطة النهاية التي تم حلها</string>
|
||||
<string name="gateway_setup">إعداد البوابة</string>
|
||||
<string name="connect_to_gateway">الاتصال ببوابتك</string>
|
||||
<string name="scan_setup_code">مسح رمز الإعداد</string>
|
||||
<string name="use_gateway_qr">استخدم رمز QR الخاص ببوابتك أو رمز الإعداد</string>
|
||||
<string name="nearby_gateway">بوابة قريبة</string>
|
||||
<string name="enter_gateway_url">أدخل عنوان URL للبوابة</string>
|
||||
<string name="connect_manual_url">الاتصال باستخدام عنوان URL يدوي</string>
|
||||
<string name="permissions">الأذونات</string>
|
||||
<string name="done">تم</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Gateway-Verbindung</string>
|
||||
<string name="connect_gateway">Gateway verbinden</string>
|
||||
<string name="disconnect">Trennen</string>
|
||||
<string name="trust_this_gateway">Diesem Gateway vertrauen?</string>
|
||||
<string name="trust_and_continue">Vertrauen und fortfahren</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="endpoint">Endpunkt</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="connected_gateway_ready">Ihr Gateway ist aktiv und bereit.</string>
|
||||
<string name="connect_gateway_get_started">Verbinden Sie sich mit Ihrem Gateway, um loszulegen.</string>
|
||||
<string name="copy_report_for_claw">Bericht für Claw kopieren</string>
|
||||
<string name="advanced_controls">Erweiterte Steuerungen</string>
|
||||
<string name="connection_method">Verbindungsmethode</string>
|
||||
<string name="setup_code">Einrichtungscode</string>
|
||||
<string name="manual">Manuell</string>
|
||||
<string name="paste_setup_code">Einrichtungscode einfügen</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">TLS verwenden</string>
|
||||
<string name="token_optional">Token (optional)</string>
|
||||
<string name="password">Passwort</string>
|
||||
<string name="run_onboarding_again">Onboarding erneut ausführen</string>
|
||||
<string name="resolved_endpoint">Aufgelöster Endpunkt</string>
|
||||
<string name="gateway_setup">Gateway-Einrichtung</string>
|
||||
<string name="connect_to_gateway">Mit Ihrem Gateway verbinden</string>
|
||||
<string name="scan_setup_code">Einrichtungscode scannen</string>
|
||||
<string name="use_gateway_qr">Verwenden Sie Ihren Gateway-QR- oder Einrichtungscode</string>
|
||||
<string name="nearby_gateway">Gateway in der Nähe</string>
|
||||
<string name="enter_gateway_url">Gateway-URL eingeben</string>
|
||||
<string name="connect_manual_url">Über eine manuelle URL verbinden</string>
|
||||
<string name="permissions">Berechtigungen</string>
|
||||
<string name="done">Fertig</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Conexión de Gateway</string>
|
||||
<string name="connect_gateway">Conectar Gateway</string>
|
||||
<string name="disconnect">Desconectar</string>
|
||||
<string name="trust_this_gateway">¿Confiar en este gateway?</string>
|
||||
<string name="trust_and_continue">Confiar y continuar</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="status">Estado</string>
|
||||
<string name="connected_gateway_ready">Tu gateway está activo y listo.</string>
|
||||
<string name="connect_gateway_get_started">Conéctate a tu gateway para empezar.</string>
|
||||
<string name="copy_report_for_claw">Copiar informe para Claw</string>
|
||||
<string name="advanced_controls">Controles avanzados</string>
|
||||
<string name="connection_method">Método de conexión</string>
|
||||
<string name="setup_code">Código de configuración</string>
|
||||
<string name="manual">Manual</string>
|
||||
<string name="paste_setup_code">Pegar código de configuración</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">Usar TLS</string>
|
||||
<string name="token_optional">Token (opcional)</string>
|
||||
<string name="password">Contraseña</string>
|
||||
<string name="run_onboarding_again">Ejecutar la incorporación de nuevo</string>
|
||||
<string name="resolved_endpoint">Endpoint resuelto</string>
|
||||
<string name="gateway_setup">Configuración de Gateway</string>
|
||||
<string name="connect_to_gateway">Conéctate a tu Gateway</string>
|
||||
<string name="scan_setup_code">Escanear código de configuración</string>
|
||||
<string name="use_gateway_qr">Usa el QR o código de configuración de tu Gateway</string>
|
||||
<string name="nearby_gateway">Gateway cercano</string>
|
||||
<string name="enter_gateway_url">Introduce la URL del gateway</string>
|
||||
<string name="connect_manual_url">Conectar usando una URL manual</string>
|
||||
<string name="permissions">Permisos</string>
|
||||
<string name="done">Listo</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">اتصال دروازه</string>
|
||||
<string name="connect_gateway">اتصال به دروازه</string>
|
||||
<string name="disconnect">قطع اتصال</string>
|
||||
<string name="trust_this_gateway">به این دروازه اعتماد دارید؟</string>
|
||||
<string name="trust_and_continue">اعتماد و ادامه</string>
|
||||
<string name="cancel">لغو</string>
|
||||
<string name="endpoint">نقطه پایانی</string>
|
||||
<string name="status">وضعیت</string>
|
||||
<string name="connected_gateway_ready">دروازه شما فعال و آماده است.</string>
|
||||
<string name="connect_gateway_get_started">برای شروع، به دروازه خود متصل شوید.</string>
|
||||
<string name="copy_report_for_claw">کپی گزارش برای Claw</string>
|
||||
<string name="advanced_controls">کنترلهای پیشرفته</string>
|
||||
<string name="connection_method">روش اتصال</string>
|
||||
<string name="setup_code">کد راهاندازی</string>
|
||||
<string name="manual">دستی</string>
|
||||
<string name="paste_setup_code">کد راهاندازی را جایگذاری کنید</string>
|
||||
<string name="host">میزبان</string>
|
||||
<string name="use_tls">استفاده از TLS</string>
|
||||
<string name="token_optional">توکن (اختیاری)</string>
|
||||
<string name="password">رمز عبور</string>
|
||||
<string name="run_onboarding_again">اجرای دوباره فرایند شروع به کار</string>
|
||||
<string name="resolved_endpoint">نقطه پایانی حلشده</string>
|
||||
<string name="gateway_setup">راهاندازی دروازه</string>
|
||||
<string name="connect_to_gateway">به دروازه خود متصل شوید</string>
|
||||
<string name="scan_setup_code">اسکن کد راهاندازی</string>
|
||||
<string name="use_gateway_qr">از QR دروازه یا کد راهاندازی خود استفاده کنید</string>
|
||||
<string name="nearby_gateway">دروازه نزدیک</string>
|
||||
<string name="enter_gateway_url">URL دروازه را وارد کنید</string>
|
||||
<string name="connect_manual_url">اتصال با استفاده از URL دستی</string>
|
||||
<string name="permissions">مجوزها</string>
|
||||
<string name="done">انجام شد</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Connexion à la passerelle</string>
|
||||
<string name="connect_gateway">Connecter la passerelle</string>
|
||||
<string name="disconnect">Déconnecter</string>
|
||||
<string name="trust_this_gateway">Faire confiance à cette passerelle ?</string>
|
||||
<string name="trust_and_continue">Faire confiance et continuer</string>
|
||||
<string name="cancel">Annuler</string>
|
||||
<string name="endpoint">Point de terminaison</string>
|
||||
<string name="status">État</string>
|
||||
<string name="connected_gateway_ready">Votre passerelle est active et prête.</string>
|
||||
<string name="connect_gateway_get_started">Connectez-vous à votre passerelle pour commencer.</string>
|
||||
<string name="copy_report_for_claw">Copier le rapport pour Claw</string>
|
||||
<string name="advanced_controls">Contrôles avancés</string>
|
||||
<string name="connection_method">Méthode de connexion</string>
|
||||
<string name="setup_code">Code de configuration</string>
|
||||
<string name="manual">Manuel</string>
|
||||
<string name="paste_setup_code">Coller le code de configuration</string>
|
||||
<string name="host">Hôte</string>
|
||||
<string name="use_tls">Utiliser TLS</string>
|
||||
<string name="token_optional">Jeton (facultatif)</string>
|
||||
<string name="password">Mot de passe</string>
|
||||
<string name="run_onboarding_again">Relancer l’intégration</string>
|
||||
<string name="resolved_endpoint">Point de terminaison résolu</string>
|
||||
<string name="gateway_setup">Configuration de la passerelle</string>
|
||||
<string name="connect_to_gateway">Connectez-vous à votre Gateway</string>
|
||||
<string name="scan_setup_code">Scanner le code de configuration</string>
|
||||
<string name="use_gateway_qr">Utilisez le QR de votre Gateway ou le code de configuration</string>
|
||||
<string name="nearby_gateway">Passerelle à proximité</string>
|
||||
<string name="enter_gateway_url">Saisir l’URL de la passerelle</string>
|
||||
<string name="connect_manual_url">Se connecter avec une URL manuelle</string>
|
||||
<string name="permissions">Autorisations</string>
|
||||
<string name="done">Terminé</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">गेटवे कनेक्शन</string>
|
||||
<string name="connect_gateway">गेटवे कनेक्ट करें</string>
|
||||
<string name="disconnect">डिस्कनेक्ट करें</string>
|
||||
<string name="trust_this_gateway">इस गेटवे पर भरोसा करें?</string>
|
||||
<string name="trust_and_continue">भरोसा करें और जारी रखें</string>
|
||||
<string name="cancel">रद्द करें</string>
|
||||
<string name="endpoint">एंडपॉइंट</string>
|
||||
<string name="status">स्थिति</string>
|
||||
<string name="connected_gateway_ready">आपका गेटवे सक्रिय और तैयार है।</string>
|
||||
<string name="connect_gateway_get_started">शुरू करने के लिए अपने गेटवे से कनेक्ट करें।</string>
|
||||
<string name="copy_report_for_claw">Claw के लिए रिपोर्ट कॉपी करें</string>
|
||||
<string name="advanced_controls">उन्नत नियंत्रण</string>
|
||||
<string name="connection_method">कनेक्शन विधि</string>
|
||||
<string name="setup_code">सेटअप कोड</string>
|
||||
<string name="manual">मैन्युअल</string>
|
||||
<string name="paste_setup_code">सेटअप कोड पेस्ट करें</string>
|
||||
<string name="host">होस्ट</string>
|
||||
<string name="use_tls">TLS का उपयोग करें</string>
|
||||
<string name="token_optional">टोकन (वैकल्पिक)</string>
|
||||
<string name="password">पासवर्ड</string>
|
||||
<string name="run_onboarding_again">ऑनबोर्डिंग फिर से चलाएँ</string>
|
||||
<string name="resolved_endpoint">रिज़ॉल्व किया गया एंडपॉइंट</string>
|
||||
<string name="gateway_setup">गेटवे सेटअप</string>
|
||||
<string name="connect_to_gateway">अपने गेटवे से कनेक्ट करें</string>
|
||||
<string name="scan_setup_code">सेटअप कोड स्कैन करें</string>
|
||||
<string name="use_gateway_qr">अपने गेटवे QR या सेटअप कोड का उपयोग करें</string>
|
||||
<string name="nearby_gateway">नज़दीकी गेटवे</string>
|
||||
<string name="enter_gateway_url">गेटवे URL दर्ज करें</string>
|
||||
<string name="connect_manual_url">मैन्युअल URL का उपयोग करके कनेक्ट करें</string>
|
||||
<string name="permissions">अनुमतियाँ</string>
|
||||
<string name="done">हो गया</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Koneksi Gateway</string>
|
||||
<string name="connect_gateway">Hubungkan Gateway</string>
|
||||
<string name="disconnect">Putuskan koneksi</string>
|
||||
<string name="trust_this_gateway">Percayai gateway ini?</string>
|
||||
<string name="trust_and_continue">Percayai dan lanjutkan</string>
|
||||
<string name="cancel">Batal</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="connected_gateway_ready">Gateway Anda aktif dan siap.</string>
|
||||
<string name="connect_gateway_get_started">Hubungkan ke gateway Anda untuk memulai.</string>
|
||||
<string name="copy_report_for_claw">Salin Laporan untuk Claw</string>
|
||||
<string name="advanced_controls">Kontrol lanjutan</string>
|
||||
<string name="connection_method">Metode koneksi</string>
|
||||
<string name="setup_code">Kode Penyiapan</string>
|
||||
<string name="manual">Manual</string>
|
||||
<string name="paste_setup_code">Tempel kode penyiapan</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">Gunakan TLS</string>
|
||||
<string name="token_optional">Token (opsional)</string>
|
||||
<string name="password">Kata sandi</string>
|
||||
<string name="run_onboarding_again">Jalankan onboarding lagi</string>
|
||||
<string name="resolved_endpoint">Endpoint yang diselesaikan</string>
|
||||
<string name="gateway_setup">Penyiapan Gateway</string>
|
||||
<string name="connect_to_gateway">Hubungkan ke Gateway Anda</string>
|
||||
<string name="scan_setup_code">Pindai kode penyiapan</string>
|
||||
<string name="use_gateway_qr">Gunakan QR Gateway atau kode penyiapan Anda</string>
|
||||
<string name="nearby_gateway">Gateway terdekat</string>
|
||||
<string name="enter_gateway_url">Masukkan URL gateway</string>
|
||||
<string name="connect_manual_url">Hubungkan menggunakan URL manual</string>
|
||||
<string name="permissions">Izin</string>
|
||||
<string name="done">Selesai</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Connessione al gateway</string>
|
||||
<string name="connect_gateway">Connetti gateway</string>
|
||||
<string name="disconnect">Disconnetti</string>
|
||||
<string name="trust_this_gateway">Considerare attendibile questo gateway?</string>
|
||||
<string name="trust_and_continue">Considera attendibile e continua</string>
|
||||
<string name="cancel">Annulla</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="status">Stato</string>
|
||||
<string name="connected_gateway_ready">Il tuo gateway è attivo e pronto.</string>
|
||||
<string name="connect_gateway_get_started">Connettiti al tuo gateway per iniziare.</string>
|
||||
<string name="copy_report_for_claw">Copia report per Claw</string>
|
||||
<string name="advanced_controls">Controlli avanzati</string>
|
||||
<string name="connection_method">Metodo di connessione</string>
|
||||
<string name="setup_code">Codice di configurazione</string>
|
||||
<string name="manual">Manuale</string>
|
||||
<string name="paste_setup_code">Incolla codice di configurazione</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">Usa TLS</string>
|
||||
<string name="token_optional">Token (opzionale)</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="run_onboarding_again">Esegui di nuovo l'onboarding</string>
|
||||
<string name="resolved_endpoint">Endpoint risolto</string>
|
||||
<string name="gateway_setup">Configurazione gateway</string>
|
||||
<string name="connect_to_gateway">Connettiti al tuo Gateway</string>
|
||||
<string name="scan_setup_code">Scansiona codice di configurazione</string>
|
||||
<string name="use_gateway_qr">Usa il QR del tuo Gateway o il codice di configurazione</string>
|
||||
<string name="nearby_gateway">Gateway nelle vicinanze</string>
|
||||
<string name="enter_gateway_url">Inserisci URL del gateway</string>
|
||||
<string name="connect_manual_url">Connetti usando un URL manuale</string>
|
||||
<string name="permissions">Autorizzazioni</string>
|
||||
<string name="done">Fine</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">ゲートウェイ接続</string>
|
||||
<string name="connect_gateway">ゲートウェイに接続</string>
|
||||
<string name="disconnect">切断</string>
|
||||
<string name="trust_this_gateway">このゲートウェイを信頼しますか?</string>
|
||||
<string name="trust_and_continue">信頼して続行</string>
|
||||
<string name="cancel">キャンセル</string>
|
||||
<string name="endpoint">エンドポイント</string>
|
||||
<string name="status">ステータス</string>
|
||||
<string name="connected_gateway_ready">ゲートウェイはアクティブで準備完了です。</string>
|
||||
<string name="connect_gateway_get_started">開始するにはゲートウェイに接続してください。</string>
|
||||
<string name="copy_report_for_claw">Claw 用レポートをコピー</string>
|
||||
<string name="advanced_controls">詳細コントロール</string>
|
||||
<string name="connection_method">接続方法</string>
|
||||
<string name="setup_code">セットアップコード</string>
|
||||
<string name="manual">手動</string>
|
||||
<string name="paste_setup_code">セットアップコードを貼り付け</string>
|
||||
<string name="host">ホスト</string>
|
||||
<string name="use_tls">TLS を使用</string>
|
||||
<string name="token_optional">トークン(任意)</string>
|
||||
<string name="password">パスワード</string>
|
||||
<string name="run_onboarding_again">オンボーディングを再実行</string>
|
||||
<string name="resolved_endpoint">解決済みエンドポイント</string>
|
||||
<string name="gateway_setup">ゲートウェイ設定</string>
|
||||
<string name="connect_to_gateway">ゲートウェイに接続</string>
|
||||
<string name="scan_setup_code">セットアップコードをスキャン</string>
|
||||
<string name="use_gateway_qr">ゲートウェイの QR またはセットアップコードを使用</string>
|
||||
<string name="nearby_gateway">近くのゲートウェイ</string>
|
||||
<string name="enter_gateway_url">ゲートウェイ URL を入力</string>
|
||||
<string name="connect_manual_url">手動 URL で接続</string>
|
||||
<string name="permissions">権限</string>
|
||||
<string name="done">完了</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">게이트웨이 연결</string>
|
||||
<string name="connect_gateway">게이트웨이 연결</string>
|
||||
<string name="disconnect">연결 해제</string>
|
||||
<string name="trust_this_gateway">이 게이트웨이를 신뢰하시겠습니까?</string>
|
||||
<string name="trust_and_continue">신뢰하고 계속</string>
|
||||
<string name="cancel">취소</string>
|
||||
<string name="endpoint">엔드포인트</string>
|
||||
<string name="status">상태</string>
|
||||
<string name="connected_gateway_ready">게이트웨이가 활성화되어 준비되었습니다.</string>
|
||||
<string name="connect_gateway_get_started">시작하려면 게이트웨이에 연결하세요.</string>
|
||||
<string name="copy_report_for_claw">Claw용 보고서 복사</string>
|
||||
<string name="advanced_controls">고급 제어</string>
|
||||
<string name="connection_method">연결 방법</string>
|
||||
<string name="setup_code">설정 코드</string>
|
||||
<string name="manual">수동</string>
|
||||
<string name="paste_setup_code">설정 코드 붙여넣기</string>
|
||||
<string name="host">호스트</string>
|
||||
<string name="use_tls">TLS 사용</string>
|
||||
<string name="token_optional">토큰(선택 사항)</string>
|
||||
<string name="password">비밀번호</string>
|
||||
<string name="run_onboarding_again">온보딩 다시 실행</string>
|
||||
<string name="resolved_endpoint">확인된 엔드포인트</string>
|
||||
<string name="gateway_setup">게이트웨이 설정</string>
|
||||
<string name="connect_to_gateway">게이트웨이에 연결</string>
|
||||
<string name="scan_setup_code">설정 코드 스캔</string>
|
||||
<string name="use_gateway_qr">게이트웨이 QR 또는 설정 코드 사용</string>
|
||||
<string name="nearby_gateway">주변 게이트웨이</string>
|
||||
<string name="enter_gateway_url">게이트웨이 URL 입력</string>
|
||||
<string name="connect_manual_url">수동 URL을 사용하여 연결</string>
|
||||
<string name="permissions">권한</string>
|
||||
<string name="done">완료</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Gatewayverbinding</string>
|
||||
<string name="connect_gateway">Gateway verbinden</string>
|
||||
<string name="disconnect">Verbinding verbreken</string>
|
||||
<string name="trust_this_gateway">Deze gateway vertrouwen?</string>
|
||||
<string name="trust_and_continue">Vertrouwen en doorgaan</string>
|
||||
<string name="cancel">Annuleren</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="connected_gateway_ready">Je gateway is actief en klaar voor gebruik.</string>
|
||||
<string name="connect_gateway_get_started">Verbind met je gateway om te beginnen.</string>
|
||||
<string name="copy_report_for_claw">Rapport voor Claw kopiëren</string>
|
||||
<string name="advanced_controls">Geavanceerde bediening</string>
|
||||
<string name="connection_method">Verbindingsmethode</string>
|
||||
<string name="setup_code">Setupcode</string>
|
||||
<string name="manual">Handmatig</string>
|
||||
<string name="paste_setup_code">Setupcode plakken</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">TLS gebruiken</string>
|
||||
<string name="token_optional">Token (optioneel)</string>
|
||||
<string name="password">Wachtwoord</string>
|
||||
<string name="run_onboarding_again">Onboarding opnieuw uitvoeren</string>
|
||||
<string name="resolved_endpoint">Opgelost endpoint</string>
|
||||
<string name="gateway_setup">Gateway instellen</string>
|
||||
<string name="connect_to_gateway">Verbinden met je Gateway</string>
|
||||
<string name="scan_setup_code">Setupcode scannen</string>
|
||||
<string name="use_gateway_qr">Gebruik je Gateway-QR-code of setupcode</string>
|
||||
<string name="nearby_gateway">Gateway in de buurt</string>
|
||||
<string name="enter_gateway_url">Gateway-URL invoeren</string>
|
||||
<string name="connect_manual_url">Verbinden met een handmatige URL</string>
|
||||
<string name="permissions">Machtigingen</string>
|
||||
<string name="done">Gereed</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Połączenie z bramą</string>
|
||||
<string name="connect_gateway">Połącz z bramą</string>
|
||||
<string name="disconnect">Rozłącz</string>
|
||||
<string name="trust_this_gateway">Ufać tej bramie?</string>
|
||||
<string name="trust_and_continue">Zaufaj i kontynuuj</string>
|
||||
<string name="cancel">Anuluj</string>
|
||||
<string name="endpoint">Punkt końcowy</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="connected_gateway_ready">Twoja brama jest aktywna i gotowa.</string>
|
||||
<string name="connect_gateway_get_started">Połącz się ze swoją bramą, aby rozpocząć.</string>
|
||||
<string name="copy_report_for_claw">Kopiuj raport dla Claw</string>
|
||||
<string name="advanced_controls">Zaawansowane ustawienia</string>
|
||||
<string name="connection_method">Metoda połączenia</string>
|
||||
<string name="setup_code">Kod konfiguracji</string>
|
||||
<string name="manual">Ręcznie</string>
|
||||
<string name="paste_setup_code">Wklej kod konfiguracji</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">Użyj TLS</string>
|
||||
<string name="token_optional">Token (opcjonalnie)</string>
|
||||
<string name="password">Hasło</string>
|
||||
<string name="run_onboarding_again">Uruchom ponownie wdrażanie</string>
|
||||
<string name="resolved_endpoint">Rozpoznany punkt końcowy</string>
|
||||
<string name="gateway_setup">Konfiguracja bramy</string>
|
||||
<string name="connect_to_gateway">Połącz ze swoją bramą</string>
|
||||
<string name="scan_setup_code">Zeskanuj kod konfiguracji</string>
|
||||
<string name="use_gateway_qr">Użyj kodu QR bramy lub kodu konfiguracji</string>
|
||||
<string name="nearby_gateway">Pobliska brama</string>
|
||||
<string name="enter_gateway_url">Wprowadź URL bramy</string>
|
||||
<string name="connect_manual_url">Połącz, używając ręcznego URL</string>
|
||||
<string name="permissions">Uprawnienia</string>
|
||||
<string name="done">Gotowe</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Conexão do Gateway</string>
|
||||
<string name="connect_gateway">Conectar Gateway</string>
|
||||
<string name="disconnect">Desconectar</string>
|
||||
<string name="trust_this_gateway">Confiar neste gateway?</string>
|
||||
<string name="trust_and_continue">Confiar e continuar</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="connected_gateway_ready">Seu gateway está ativo e pronto.</string>
|
||||
<string name="connect_gateway_get_started">Conecte-se ao seu gateway para começar.</string>
|
||||
<string name="copy_report_for_claw">Copiar relatório para o Claw</string>
|
||||
<string name="advanced_controls">Controles avançados</string>
|
||||
<string name="connection_method">Método de conexão</string>
|
||||
<string name="setup_code">Código de configuração</string>
|
||||
<string name="manual">Manual</string>
|
||||
<string name="paste_setup_code">Colar código de configuração</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">Usar TLS</string>
|
||||
<string name="token_optional">Token (opcional)</string>
|
||||
<string name="password">Senha</string>
|
||||
<string name="run_onboarding_again">Executar integração novamente</string>
|
||||
<string name="resolved_endpoint">Endpoint resolvido</string>
|
||||
<string name="gateway_setup">Configuração do Gateway</string>
|
||||
<string name="connect_to_gateway">Conecte-se ao seu Gateway</string>
|
||||
<string name="scan_setup_code">Escanear código de configuração</string>
|
||||
<string name="use_gateway_qr">Use o QR do seu Gateway ou o código de configuração</string>
|
||||
<string name="nearby_gateway">Gateway próximo</string>
|
||||
<string name="enter_gateway_url">Inserir URL do gateway</string>
|
||||
<string name="connect_manual_url">Conectar usando uma URL manual</string>
|
||||
<string name="permissions">Permissões</string>
|
||||
<string name="done">Concluído</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Подключение к шлюзу</string>
|
||||
<string name="connect_gateway">Подключить шлюз</string>
|
||||
<string name="disconnect">Отключить</string>
|
||||
<string name="trust_this_gateway">Доверять этому шлюзу?</string>
|
||||
<string name="trust_and_continue">Доверять и продолжить</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="endpoint">Конечная точка</string>
|
||||
<string name="status">Статус</string>
|
||||
<string name="connected_gateway_ready">Ваш шлюз активен и готов.</string>
|
||||
<string name="connect_gateway_get_started">Подключитесь к своему шлюзу, чтобы начать.</string>
|
||||
<string name="copy_report_for_claw">Скопировать отчет для Claw</string>
|
||||
<string name="advanced_controls">Расширенные настройки</string>
|
||||
<string name="connection_method">Способ подключения</string>
|
||||
<string name="setup_code">Код настройки</string>
|
||||
<string name="manual">Вручную</string>
|
||||
<string name="paste_setup_code">Вставьте код настройки</string>
|
||||
<string name="host">Хост</string>
|
||||
<string name="use_tls">Использовать TLS</string>
|
||||
<string name="token_optional">Токен (необязательно)</string>
|
||||
<string name="password">Пароль</string>
|
||||
<string name="run_onboarding_again">Запустить настройку заново</string>
|
||||
<string name="resolved_endpoint">Разрешенная конечная точка</string>
|
||||
<string name="gateway_setup">Настройка шлюза</string>
|
||||
<string name="connect_to_gateway">Подключитесь к своему шлюзу</string>
|
||||
<string name="scan_setup_code">Сканировать код настройки</string>
|
||||
<string name="use_gateway_qr">Используйте QR-код или код настройки вашего шлюза</string>
|
||||
<string name="nearby_gateway">Шлюз поблизости</string>
|
||||
<string name="enter_gateway_url">Введите URL шлюза</string>
|
||||
<string name="connect_manual_url">Подключиться с помощью URL вручную</string>
|
||||
<string name="permissions">Разрешения</string>
|
||||
<string name="done">Готово</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">การเชื่อมต่อเกตเวย์</string>
|
||||
<string name="connect_gateway">เชื่อมต่อเกตเวย์</string>
|
||||
<string name="disconnect">ตัดการเชื่อมต่อ</string>
|
||||
<string name="trust_this_gateway">เชื่อถือเกตเวย์นี้หรือไม่?</string>
|
||||
<string name="trust_and_continue">เชื่อถือและดำเนินการต่อ</string>
|
||||
<string name="cancel">ยกเลิก</string>
|
||||
<string name="endpoint">เอนด์พอยต์</string>
|
||||
<string name="status">สถานะ</string>
|
||||
<string name="connected_gateway_ready">เกตเวย์ของคุณเปิดใช้งานและพร้อมใช้งานแล้ว</string>
|
||||
<string name="connect_gateway_get_started">เชื่อมต่อกับเกตเวย์ของคุณเพื่อเริ่มต้นใช้งาน</string>
|
||||
<string name="copy_report_for_claw">คัดลอกรายงานสำหรับ Claw</string>
|
||||
<string name="advanced_controls">การควบคุมขั้นสูง</string>
|
||||
<string name="connection_method">วิธีการเชื่อมต่อ</string>
|
||||
<string name="setup_code">รหัสตั้งค่า</string>
|
||||
<string name="manual">ด้วยตนเอง</string>
|
||||
<string name="paste_setup_code">วางรหัสตั้งค่า</string>
|
||||
<string name="host">โฮสต์</string>
|
||||
<string name="use_tls">ใช้ TLS</string>
|
||||
<string name="token_optional">โทเค็น (ไม่บังคับ)</string>
|
||||
<string name="password">รหัสผ่าน</string>
|
||||
<string name="run_onboarding_again">เรียกใช้การเริ่มต้นใช้งานอีกครั้ง</string>
|
||||
<string name="resolved_endpoint">เอนด์พอยต์ที่แก้ไขแล้ว</string>
|
||||
<string name="gateway_setup">การตั้งค่าเกตเวย์</string>
|
||||
<string name="connect_to_gateway">เชื่อมต่อกับเกตเวย์ของคุณ</string>
|
||||
<string name="scan_setup_code">สแกนรหัสตั้งค่า</string>
|
||||
<string name="use_gateway_qr">ใช้ QR ของเกตเวย์หรือรหัสตั้งค่าของคุณ</string>
|
||||
<string name="nearby_gateway">เกตเวย์ใกล้เคียง</string>
|
||||
<string name="enter_gateway_url">ป้อน URL เกตเวย์</string>
|
||||
<string name="connect_manual_url">เชื่อมต่อโดยใช้ URL ด้วยตนเอง</string>
|
||||
<string name="permissions">สิทธิ์</string>
|
||||
<string name="done">เสร็จสิ้น</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Ağ Geçidi Bağlantısı</string>
|
||||
<string name="connect_gateway">Ağ Geçidine Bağlan</string>
|
||||
<string name="disconnect">Bağlantıyı Kes</string>
|
||||
<string name="trust_this_gateway">Bu ağ geçidine güvenilsin mi?</string>
|
||||
<string name="trust_and_continue">Güven ve devam et</string>
|
||||
<string name="cancel">İptal</string>
|
||||
<string name="endpoint">Uç nokta</string>
|
||||
<string name="status">Durum</string>
|
||||
<string name="connected_gateway_ready">Ağ geçidiniz etkin ve hazır.</string>
|
||||
<string name="connect_gateway_get_started">Başlamak için ağ geçidinize bağlanın.</string>
|
||||
<string name="copy_report_for_claw">Claw için Raporu Kopyala</string>
|
||||
<string name="advanced_controls">Gelişmiş kontroller</string>
|
||||
<string name="connection_method">Bağlantı yöntemi</string>
|
||||
<string name="setup_code">Kurulum Kodu</string>
|
||||
<string name="manual">Manuel</string>
|
||||
<string name="paste_setup_code">Kurulum kodunu yapıştır</string>
|
||||
<string name="host">Ana makine</string>
|
||||
<string name="use_tls">TLS kullan</string>
|
||||
<string name="token_optional">Token (isteğe bağlı)</string>
|
||||
<string name="password">Parola</string>
|
||||
<string name="run_onboarding_again">Başlangıç sürecini tekrar çalıştır</string>
|
||||
<string name="resolved_endpoint">Çözümlenen uç nokta</string>
|
||||
<string name="gateway_setup">Ağ Geçidi Kurulumu</string>
|
||||
<string name="connect_to_gateway">Ağ Geçidinize Bağlanın</string>
|
||||
<string name="scan_setup_code">Kurulum kodunu tara</string>
|
||||
<string name="use_gateway_qr">Gateway QR kodunuzu veya kurulum kodunuzu kullanın</string>
|
||||
<string name="nearby_gateway">Yakındaki ağ geçidi</string>
|
||||
<string name="enter_gateway_url">Ağ geçidi URL'sini girin</string>
|
||||
<string name="connect_manual_url">Manuel URL kullanarak bağlan</string>
|
||||
<string name="permissions">İzinler</string>
|
||||
<string name="done">Bitti</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Підключення до шлюзу</string>
|
||||
<string name="connect_gateway">Підключити шлюз</string>
|
||||
<string name="disconnect">Відключити</string>
|
||||
<string name="trust_this_gateway">Довіряти цьому шлюзу?</string>
|
||||
<string name="trust_and_continue">Довіряти й продовжити</string>
|
||||
<string name="cancel">Скасувати</string>
|
||||
<string name="endpoint">Кінцева точка</string>
|
||||
<string name="status">Стан</string>
|
||||
<string name="connected_gateway_ready">Ваш шлюз активний і готовий.</string>
|
||||
<string name="connect_gateway_get_started">Підключіться до свого шлюзу, щоб почати.</string>
|
||||
<string name="copy_report_for_claw">Скопіювати звіт для Claw</string>
|
||||
<string name="advanced_controls">Розширені елементи керування</string>
|
||||
<string name="connection_method">Спосіб підключення</string>
|
||||
<string name="setup_code">Код налаштування</string>
|
||||
<string name="manual">Вручну</string>
|
||||
<string name="paste_setup_code">Вставте код налаштування</string>
|
||||
<string name="host">Хост</string>
|
||||
<string name="use_tls">Використовувати TLS</string>
|
||||
<string name="token_optional">Токен (необов’язково)</string>
|
||||
<string name="password">Пароль</string>
|
||||
<string name="run_onboarding_again">Запустити адаптацію знову</string>
|
||||
<string name="resolved_endpoint">Визначена кінцева точка</string>
|
||||
<string name="gateway_setup">Налаштування шлюзу</string>
|
||||
<string name="connect_to_gateway">Підключіться до свого шлюзу</string>
|
||||
<string name="scan_setup_code">Сканувати код налаштування</string>
|
||||
<string name="use_gateway_qr">Використайте QR-код свого шлюзу або код налаштування</string>
|
||||
<string name="nearby_gateway">Шлюз поблизу</string>
|
||||
<string name="enter_gateway_url">Введіть URL-адресу шлюзу</string>
|
||||
<string name="connect_manual_url">Підключитися за допомогою URL-адреси вручну</string>
|
||||
<string name="permissions">Дозволи</string>
|
||||
<string name="done">Готово</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Kết nối cổng</string>
|
||||
<string name="connect_gateway">Kết nối cổng</string>
|
||||
<string name="disconnect">Ngắt kết nối</string>
|
||||
<string name="trust_this_gateway">Tin cậy cổng này?</string>
|
||||
<string name="trust_and_continue">Tin cậy và tiếp tục</string>
|
||||
<string name="cancel">Hủy</string>
|
||||
<string name="endpoint">Điểm cuối</string>
|
||||
<string name="status">Trạng thái</string>
|
||||
<string name="connected_gateway_ready">Cổng của bạn đang hoạt động và sẵn sàng.</string>
|
||||
<string name="connect_gateway_get_started">Kết nối với cổng của bạn để bắt đầu.</string>
|
||||
<string name="copy_report_for_claw">Sao chép báo cáo cho Claw</string>
|
||||
<string name="advanced_controls">Điều khiển nâng cao</string>
|
||||
<string name="connection_method">Phương thức kết nối</string>
|
||||
<string name="setup_code">Mã thiết lập</string>
|
||||
<string name="manual">Thủ công</string>
|
||||
<string name="paste_setup_code">Dán mã thiết lập</string>
|
||||
<string name="host">Máy chủ</string>
|
||||
<string name="use_tls">Sử dụng TLS</string>
|
||||
<string name="token_optional">Token (tùy chọn)</string>
|
||||
<string name="password">Mật khẩu</string>
|
||||
<string name="run_onboarding_again">Chạy hướng dẫn thiết lập lại</string>
|
||||
<string name="resolved_endpoint">Điểm cuối đã phân giải</string>
|
||||
<string name="gateway_setup">Thiết lập cổng</string>
|
||||
<string name="connect_to_gateway">Kết nối với Gateway của bạn</string>
|
||||
<string name="scan_setup_code">Quét mã thiết lập</string>
|
||||
<string name="use_gateway_qr">Sử dụng mã QR Gateway hoặc mã thiết lập của bạn</string>
|
||||
<string name="nearby_gateway">Cổng gần đây</string>
|
||||
<string name="enter_gateway_url">Nhập URL cổng</string>
|
||||
<string name="connect_manual_url">Kết nối bằng URL thủ công</string>
|
||||
<string name="permissions">Quyền</string>
|
||||
<string name="done">Xong</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">网关连接</string>
|
||||
<string name="connect_gateway">连接网关</string>
|
||||
<string name="disconnect">断开连接</string>
|
||||
<string name="trust_this_gateway">信任此网关?</string>
|
||||
<string name="trust_and_continue">信任并继续</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="endpoint">端点</string>
|
||||
<string name="status">状态</string>
|
||||
<string name="connected_gateway_ready">你的网关已激活并准备就绪。</string>
|
||||
<string name="connect_gateway_get_started">连接到你的网关以开始使用。</string>
|
||||
<string name="copy_report_for_claw">复制 Claw 报告</string>
|
||||
<string name="advanced_controls">高级控制</string>
|
||||
<string name="connection_method">连接方式</string>
|
||||
<string name="setup_code">设置代码</string>
|
||||
<string name="manual">手动</string>
|
||||
<string name="paste_setup_code">粘贴设置代码</string>
|
||||
<string name="host">主机</string>
|
||||
<string name="use_tls">使用 TLS</string>
|
||||
<string name="token_optional">令牌(可选)</string>
|
||||
<string name="password">密码</string>
|
||||
<string name="run_onboarding_again">再次运行引导流程</string>
|
||||
<string name="resolved_endpoint">已解析的端点</string>
|
||||
<string name="gateway_setup">网关设置</string>
|
||||
<string name="connect_to_gateway">连接到你的网关</string>
|
||||
<string name="scan_setup_code">扫描设置代码</string>
|
||||
<string name="use_gateway_qr">使用你的网关 QR 码或设置代码</string>
|
||||
<string name="nearby_gateway">附近的网关</string>
|
||||
<string name="enter_gateway_url">输入网关 URL</string>
|
||||
<string name="connect_manual_url">使用手动 URL 连接</string>
|
||||
<string name="permissions">权限</string>
|
||||
<string name="done">完成</string>
|
||||
</resources>
|
||||
@@ -1,34 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">閘道連線</string>
|
||||
<string name="connect_gateway">連接閘道</string>
|
||||
<string name="disconnect">中斷連線</string>
|
||||
<string name="trust_this_gateway">信任此閘道?</string>
|
||||
<string name="trust_and_continue">信任並繼續</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="endpoint">端點</string>
|
||||
<string name="status">狀態</string>
|
||||
<string name="connected_gateway_ready">您的閘道已啟用並準備就緒。</string>
|
||||
<string name="connect_gateway_get_started">連接到您的閘道以開始使用。</string>
|
||||
<string name="copy_report_for_claw">複製 Claw 報告</string>
|
||||
<string name="advanced_controls">進階控制項</string>
|
||||
<string name="connection_method">連線方式</string>
|
||||
<string name="setup_code">設定碼</string>
|
||||
<string name="manual">手動</string>
|
||||
<string name="paste_setup_code">貼上設定碼</string>
|
||||
<string name="host">主機</string>
|
||||
<string name="use_tls">使用 TLS</string>
|
||||
<string name="token_optional">權杖(選填)</string>
|
||||
<string name="password">密碼</string>
|
||||
<string name="run_onboarding_again">再次執行新手導覽</string>
|
||||
<string name="resolved_endpoint">已解析的端點</string>
|
||||
<string name="gateway_setup">閘道設定</string>
|
||||
<string name="connect_to_gateway">連接到您的閘道</string>
|
||||
<string name="scan_setup_code">掃描設定碼</string>
|
||||
<string name="use_gateway_qr">使用您的 Gateway QR 或設定碼</string>
|
||||
<string name="nearby_gateway">附近的閘道</string>
|
||||
<string name="enter_gateway_url">輸入閘道 URL</string>
|
||||
<string name="connect_manual_url">使用手動 URL 連接</string>
|
||||
<string name="permissions">權限</string>
|
||||
<string name="done">完成</string>
|
||||
</resources>
|
||||
@@ -1,34 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">OpenClaw Node</string>
|
||||
<string name="gateway_connection">Gateway Connection</string>
|
||||
<string name="connect_gateway">Connect Gateway</string>
|
||||
<string name="disconnect">Disconnect</string>
|
||||
<string name="trust_this_gateway">Trust this gateway?</string>
|
||||
<string name="trust_and_continue">Trust and continue</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="endpoint">Endpoint</string>
|
||||
<string name="status">Status</string>
|
||||
<string name="connected_gateway_ready">Your gateway is active and ready.</string>
|
||||
<string name="connect_gateway_get_started">Connect to your gateway to get started.</string>
|
||||
<string name="copy_report_for_claw">Copy Report for Claw</string>
|
||||
<string name="advanced_controls">Advanced controls</string>
|
||||
<string name="connection_method">Connection method</string>
|
||||
<string name="setup_code">Setup Code</string>
|
||||
<string name="manual">Manual</string>
|
||||
<string name="paste_setup_code">Paste setup code</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="use_tls">Use TLS</string>
|
||||
<string name="token_optional">Token (optional)</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="run_onboarding_again">Run onboarding again</string>
|
||||
<string name="resolved_endpoint">Resolved endpoint</string>
|
||||
<string name="gateway_setup">Gateway Setup</string>
|
||||
<string name="connect_to_gateway">Connect to your Gateway</string>
|
||||
<string name="scan_setup_code">Scan setup code</string>
|
||||
<string name="use_gateway_qr">Use your Gateway QR or setup code</string>
|
||||
<string name="nearby_gateway">Nearby gateway</string>
|
||||
<string name="enter_gateway_url">Enter gateway URL</string>
|
||||
<string name="connect_manual_url">Connect using a manual URL</string>
|
||||
<string name="permissions">Permissions</string>
|
||||
<string name="done">Done</string>
|
||||
</resources>
|
||||
|
||||
@@ -198,58 +198,6 @@ def capture_android_screenshots!
|
||||
sh(shell_join(["bash", File.join(repo_root, "scripts", "android-screenshots.sh")]))
|
||||
end
|
||||
|
||||
def mobile_release_ref_script
|
||||
File.join(repo_root, "scripts", "mobile-release-ref.ts")
|
||||
end
|
||||
|
||||
def release_git_sha
|
||||
stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
|
||||
UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
|
||||
stdout.strip
|
||||
end
|
||||
|
||||
def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
args = [
|
||||
"node",
|
||||
"--import",
|
||||
"tsx",
|
||||
mobile_release_ref_script,
|
||||
command,
|
||||
"--platform",
|
||||
platform,
|
||||
"--version",
|
||||
version,
|
||||
"--root",
|
||||
repo_root,
|
||||
]
|
||||
args.push("--build", build.to_s) if build
|
||||
args.push("--version-code", version_code.to_s) if version_code
|
||||
args.push("--sha", sha.to_s) if sha
|
||||
sh(shell_join(args))
|
||||
end
|
||||
|
||||
def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"preflight",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"record",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def read_android_release_signing_properties!(path)
|
||||
UI.user_error!("Missing materialized Android release signing properties at #{path}.") unless File.exist?(path)
|
||||
|
||||
@@ -334,13 +282,6 @@ def upload_play_store_metadata!(version_metadata)
|
||||
end
|
||||
|
||||
def upload_play_store_build!(version_metadata, upload_metadata: false, upload_images: false, upload_screenshots: false)
|
||||
release_sha = release_git_sha
|
||||
ensure_mobile_release_ref_available!(
|
||||
platform: "android",
|
||||
version: version_metadata.fetch(:version),
|
||||
version_code: version_metadata.fetch(:version_code),
|
||||
sha: release_sha
|
||||
)
|
||||
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1" if upload_screenshots
|
||||
validate_android_screenshots!
|
||||
sync_android_changelog!(version_metadata.fetch(:version_code))
|
||||
@@ -361,15 +302,6 @@ def upload_play_store_build!(version_metadata, upload_metadata: false, upload_im
|
||||
skip_upload_screenshots: !upload_screenshots,
|
||||
validate_only: play_validate_only?
|
||||
)
|
||||
|
||||
unless play_validate_only?
|
||||
record_mobile_release_ref!(
|
||||
platform: "android",
|
||||
version: version_metadata.fetch(:version),
|
||||
version_code: version_metadata.fetch(:version_code),
|
||||
sha: release_sha
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
load_env_file(File.join(ANDROID_FASTLANE_ROOT, ".env"))
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Adds settings detail panels, refreshes the Android overview controls, and routes exec approvals into the in-app inbox.
|
||||
|
||||
Improves chat acknowledgement handling, gateway pairing readiness, microphone foreground-service behavior, and release screenshot reliability.
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "2026.6.10",
|
||||
"versionCode": 2026061001
|
||||
"version": "2026.6.9",
|
||||
"versionCode": 2026060901
|
||||
}
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
# App Review Notes
|
||||
|
||||
Use these steps to exercise the live OpenClaw iOS App Review Gateway.
|
||||
|
||||
## Demo Account / Setup
|
||||
|
||||
Use the OpenClaw iOS app with the live review Gateway setup code included in
|
||||
the `Notes` field of this App Review submission.
|
||||
|
||||
The setup code is a single generated code string. It already contains the public
|
||||
Gateway host and setup credential.
|
||||
|
||||
## Setup Walkthrough
|
||||
|
||||
1. Open the OpenClaw app.
|
||||
2. Tap `Continue`.
|
||||
3. On `Connect Gateway`, tap `Set Up Manually`.
|
||||
4. In the `Setup Code` section, tap the `Paste setup code` field.
|
||||
5. Paste the setup code string from the App Review submission `Notes` field.
|
||||
6. Tap `Apply Setup Code`.
|
||||
7. If `Trust and connect` appears, tap `Trust and connect`.
|
||||
8. Wait for the `Connected` screen.
|
||||
9. On `Connected`, tap `Open OpenClaw`.
|
||||
10. Confirm the `Control` screen shows `Gateway Online`.
|
||||
11. Tap `Settings`.
|
||||
12. Tap `Approvals`.
|
||||
13. Tap `Open Notifications`.
|
||||
14. Tap `Enable Notifications`.
|
||||
15. On `Enable OpenClaw Hosted Push Relay?`, tap `Continue`.
|
||||
16. If iOS asks whether OpenClaw may send notifications, tap `Allow`.
|
||||
17. Confirm `Notifications` shows `Enabled`.
|
||||
|
||||
## Chat
|
||||
|
||||
1. Tap the `Chat` tab.
|
||||
2. Tap the text field labeled `Message main...`.
|
||||
3. Send this exact message:
|
||||
|
||||
```text
|
||||
Start Apple review checklist.
|
||||
```
|
||||
|
||||
Expected result: the assistant replies with the available App Review demos.
|
||||
|
||||
## Approval Demo
|
||||
|
||||
1. Tap the `Chat` tab.
|
||||
2. Tap the text field labeled `Message main...`.
|
||||
3. Send this exact message:
|
||||
|
||||
```text
|
||||
Run the approval demo.
|
||||
```
|
||||
|
||||
Expected result: the iPhone shows `Exec approval required` with the harmless
|
||||
command `printf 'OpenClaw App Review approval demo complete\n'`. Tap
|
||||
`Allow Once`. The chat then replies:
|
||||
|
||||
```text
|
||||
The approval demo completed.
|
||||
```
|
||||
|
||||
## Talk
|
||||
|
||||
1. Tap the `Talk` tab.
|
||||
2. Tap `Start Talk`.
|
||||
3. If iOS asks for microphone access, tap `Allow`.
|
||||
4. If iOS asks for Speech Recognition access, tap `Allow`.
|
||||
5. Confirm the screen changes to `Ready to talk` and shows `Stop Talk`.
|
||||
6. Say:
|
||||
|
||||
```text
|
||||
Summarize this review setup in one sentence.
|
||||
```
|
||||
|
||||
Expected result: the assistant responds by voice. Tap `Stop Talk` when done.
|
||||
|
||||
## Talk + Background Audio
|
||||
|
||||
1. Tap the `Talk` tab.
|
||||
2. Confirm `Speakerphone` is on.
|
||||
3. Confirm `Background listening` is on.
|
||||
4. Tap `Start Talk`.
|
||||
5. If iOS asks for microphone access, tap `Allow`.
|
||||
6. If iOS asks for Speech Recognition access, tap `Allow`.
|
||||
7. Confirm `Stop Talk` is visible.
|
||||
8. Say:
|
||||
|
||||
```text
|
||||
Tell me when you can hear me.
|
||||
```
|
||||
|
||||
9. While Talk is active, send OpenClaw to the background by returning to the
|
||||
Home Screen or locking the iPhone. Do not force quit the app.
|
||||
10. Continue speaking then wait for assistant audio reply.
|
||||
|
||||
Expected result: realtime Talk audio continues while OpenClaw is backgrounded.
|
||||
Reopen OpenClaw, confirm Talk is still active, then tap `Stop Talk`.
|
||||
|
||||
## Gateway Status
|
||||
|
||||
1. Tap `Control`.
|
||||
2. Tap `Instances`.
|
||||
3. Confirm the screen shows `Gateway online`.
|
||||
4. Confirm at least one `agent` row is connected.
|
||||
5. Confirm the iPhone review device appears in the connected instances list.
|
||||
|
||||
## Live Activity / Dynamic Island
|
||||
|
||||
1. Tap `Settings`.
|
||||
2. Tap `Reconnect`.
|
||||
3. Immediately send OpenClaw to the background by returning to the Home Screen
|
||||
or locking the iPhone.
|
||||
4. Watch the Lock Screen or Dynamic Island while the Gateway reconnects.
|
||||
|
||||
Expected result: while reconnecting, iOS can show an `OpenClaw` Live Activity
|
||||
with connection status such as `Connecting...` or `Reconnecting...`. On a fast
|
||||
network this status may be brief because OpenClaw ends the Live Activity after
|
||||
the Gateway reconnects successfully.
|
||||
|
||||
## Push Notification
|
||||
|
||||
1. Tap the `Chat` tab.
|
||||
2. Tap the text field labeled `Message main...`.
|
||||
3. Send this exact message:
|
||||
|
||||
```text
|
||||
Start push notification demo.
|
||||
```
|
||||
|
||||
4. Immediately send OpenClaw to the background and lock the iPhone. Do not
|
||||
force quit the app.
|
||||
|
||||
Expected result: the iPhone Lock Screen receives a visible `OpenClaw`
|
||||
notification with this body:
|
||||
|
||||
```text
|
||||
OpenClaw App Review push notification demo
|
||||
```
|
||||
|
||||
Tap the notification and unlock the iPhone if prompted. If OpenClaw opens on
|
||||
`Control`, tap `Chat`. Expected chat reply:
|
||||
|
||||
```text
|
||||
The push notification demo completed.
|
||||
```
|
||||
|
||||
## Push Wake / Status
|
||||
|
||||
1. Tap the `Chat` tab.
|
||||
2. Send this exact message:
|
||||
|
||||
```text
|
||||
Start push wake demo.
|
||||
```
|
||||
|
||||
3. Immediately send OpenClaw to the background and lock the iPhone. Do not
|
||||
force quit the app.
|
||||
4. Wait for the `OpenClaw` notification on the Lock Screen. It normally appears
|
||||
about 10 seconds after the message is sent.
|
||||
5. Tap the notification and unlock the iPhone if prompted. If OpenClaw opens on
|
||||
`Control`, tap `Chat`.
|
||||
|
||||
Expected result: the app reconnects to the live Gateway and Chat replies:
|
||||
|
||||
```text
|
||||
The push wake and node status demo completed.
|
||||
```
|
||||
|
||||
## Device Permissions
|
||||
|
||||
1. Tap `Settings`.
|
||||
2. Tap `Permissions`.
|
||||
3. Confirm these current app controls are available:
|
||||
- `Camera`
|
||||
- `Location` with `Off`, `While Using`, and `Always`
|
||||
- `Keep Awake`
|
||||
4. Expand `Privacy & Access`.
|
||||
5. Confirm these request controls are available:
|
||||
- `Contacts` / `Request Access`
|
||||
- `Calendar (Add Events)` / `Request Access`
|
||||
- `Calendar (View Events)` / `Request Full Access`
|
||||
- `Reminders` / `Request Access`
|
||||
|
||||
## Share Sheet
|
||||
|
||||
1. Open Safari.
|
||||
2. Navigate to `https://example.com`.
|
||||
3. Tap the Safari toolbar `More` button.
|
||||
4. Tap `Share`.
|
||||
5. Tap `OpenClaw`.
|
||||
6. Confirm the OpenClaw share extension appears and shows
|
||||
`Edit text, then tap Send.` and `Send to OpenClaw`.
|
||||
7. Tap `Send to OpenClaw`.
|
||||
|
||||
Expected result: the OpenClaw share extension sends the shared Safari page to
|
||||
the live review Gateway and shows `Sent to OpenClaw.` Returning to OpenClaw
|
||||
Chat shows the shared `Example Domain` page.
|
||||
@@ -1,11 +1,5 @@
|
||||
# OpenClaw iOS Changelog
|
||||
|
||||
## 2026.6.10 - 2026-06-21
|
||||
|
||||
Maintenance update for the current OpenClaw beta release.
|
||||
|
||||
- Improved notification cleanup, Watch app compatibility, and native file input handling.
|
||||
|
||||
## 2026.6.9 - 2026-06-20
|
||||
|
||||
Maintenance update for the current OpenClaw release.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Source of truth: apps/ios/version.json
|
||||
// Generated by scripts/ios-sync-versioning.ts.
|
||||
|
||||
OPENCLAW_IOS_VERSION = 2026.6.10
|
||||
OPENCLAW_MARKETING_VERSION = 2026.6.10
|
||||
OPENCLAW_IOS_VERSION = 2026.6.9
|
||||
OPENCLAW_MARKETING_VERSION = 2026.6.9
|
||||
OPENCLAW_BUILD_VERSION = 1
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -68,9 +68,9 @@ Release behavior:
|
||||
- App Store release uses manual `Apple Distribution` signing with profile names pinned in `apps/ios/Config/AppStoreSigning.json`.
|
||||
- Fastlane owns one-time Developer Portal setup, encrypted `match` signing sync to the repo/branch pinned in `apps/ios/Config/AppStoreSigning.json`, and release handling.
|
||||
- App Store release also switches the app to `OpenClawPushMode=appStore`, which derives relay transport, official distribution, the canonical production relay, production APNs, production relay profile, `appleStrict` proof, and the App-Attest-capable entitlement file.
|
||||
- `pnpm ios:release:upload` generates App Store screenshots, uploads release notes, and attaches `apps/ios/APP-REVIEW-NOTES.md` as a rendered PDF before archiving and uploading the IPA.
|
||||
- `pnpm ios:release:upload` generates App Store screenshots and uploads release notes before archiving and uploading the IPA.
|
||||
- The release archive is validated before upload by inspecting the exported IPA's signed entitlements, embedded App Store profile, and push mode. The upload fails if the IPA is not an App Store production relay build.
|
||||
- App Review submission is manual in App Store Connect. The release lane uploads a build, public metadata, and the App Review PDF attachment, but it does not submit for review or upload the App Store Connect `Notes` field.
|
||||
- App Review submission is manual in App Store Connect. The release lane uploads a build and metadata, but does not submit for review.
|
||||
- The release flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`.
|
||||
- `apps/ios/version.json` is the pinned iOS release version source.
|
||||
- `apps/ios/CHANGELOG.md` is the iOS-only changelog and release-note source.
|
||||
@@ -178,7 +178,7 @@ pnpm ios:release:upload
|
||||
- verifies synced iOS versioning artifacts
|
||||
- resolves the next App Store Connect build number for that short version
|
||||
- generates deterministic App Store screenshots
|
||||
- uploads release notes, screenshots, and the App Review PDF attachment to the editable App Store version
|
||||
- uploads release notes and screenshots to the editable App Store version
|
||||
- generates `apps/ios/build/AppStoreRelease.xcconfig`
|
||||
- archives `OpenClaw`
|
||||
- validates the exported IPA's push mode, signed entitlements, and embedded App Store profile
|
||||
|
||||
@@ -152,7 +152,6 @@ extension SettingsProTab {
|
||||
}
|
||||
let notificationSettings = await UNUserNotificationCenter.current().notificationSettings()
|
||||
self.applyNotificationStatus(notificationSettings.authorizationStatus)
|
||||
self.registerForRemoteNotificationsIfEnrollmentReady()
|
||||
|
||||
let issueCount = SettingsDiagnostics.issueCount(
|
||||
gatewayConnected: self.gatewayDiagnosticConnected,
|
||||
@@ -326,7 +325,6 @@ extension SettingsProTab {
|
||||
self.setupStatusText = "Tailscale is off on this device. Turn it on, then try again."
|
||||
return false
|
||||
}
|
||||
self.gatewayController.requestLocalNetworkAccess(reason: "settings_preflight")
|
||||
self.setupStatusText = "Checking gateway reachability..."
|
||||
let ok = await TCPProbe.probe(host: trimmed, port: port, timeoutSeconds: 3, queueLabel: "gateway.preflight")
|
||||
if !ok {
|
||||
@@ -419,7 +417,6 @@ extension SettingsProTab {
|
||||
let status = settings.authorizationStatus
|
||||
Task { @MainActor in
|
||||
self.applyNotificationStatus(status)
|
||||
self.registerForRemoteNotificationsIfEnrollmentReady()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,7 +437,6 @@ extension SettingsProTab {
|
||||
|
||||
func requestNotificationAuthorizationFromSettings() {
|
||||
guard !self.isRequestingNotificationAuthorization else { return }
|
||||
PushEnrollmentConsent.markDisclosureAccepted()
|
||||
self.isRequestingNotificationAuthorization = true
|
||||
Task {
|
||||
let granted = await (try? UNUserNotificationCenter.current().requestAuthorization(options: [
|
||||
@@ -452,19 +448,12 @@ extension SettingsProTab {
|
||||
await MainActor.run {
|
||||
self.isRequestingNotificationAuthorization = false
|
||||
self.notificationStatus = SettingsNotificationStatus(settings.authorizationStatus)
|
||||
guard granted else { return }
|
||||
self.registerForRemoteNotificationsIfEnrollmentReady()
|
||||
guard granted, self.notificationStatus.allowsNotifications else { return }
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func registerForRemoteNotificationsIfEnrollmentReady() {
|
||||
guard PushEnrollmentConsent.disclosureAccepted else { return }
|
||||
guard self.notificationStatus.allowsNotifications else { return }
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func applyNotificationStatus(_ status: UNAuthorizationStatus) {
|
||||
self.notificationStatus = SettingsNotificationStatus(status)
|
||||
|
||||
@@ -127,8 +127,6 @@ final class GatewayConnectionController {
|
||||
private let discovery = GatewayDiscoveryModel()
|
||||
private let discoveryEnabled: Bool
|
||||
private weak var appModel: NodeAppModel?
|
||||
private var localNetworkAccessRequested: Bool
|
||||
private var currentScenePhase: ScenePhase = .inactive
|
||||
private var didAutoConnect = false
|
||||
private var pendingServiceResolvers: [String: GatewayServiceResolver] = [:]
|
||||
private var pendingTrustConnect: PendingTrustConnect?
|
||||
@@ -139,14 +137,9 @@ final class GatewayConnectionController {
|
||||
let useTLS: Bool
|
||||
}
|
||||
|
||||
init(
|
||||
appModel: NodeAppModel,
|
||||
startDiscovery: Bool = true,
|
||||
deferDiscoveryUntilLocalNetworkRequest: Bool = false)
|
||||
{
|
||||
init(appModel: NodeAppModel, startDiscovery: Bool = true) {
|
||||
self.discoveryEnabled = startDiscovery
|
||||
self.appModel = appModel
|
||||
self.localNetworkAccessRequested = !deferDiscoveryUntilLocalNetworkRequest
|
||||
|
||||
GatewaySettingsStore.bootstrapPersistence()
|
||||
let defaults = UserDefaults.standard
|
||||
@@ -155,7 +148,7 @@ final class GatewayConnectionController {
|
||||
self.updateFromDiscovery()
|
||||
self.observeDiscovery()
|
||||
|
||||
if self.discoveryEnabled, self.localNetworkAccessRequested {
|
||||
if self.discoveryEnabled {
|
||||
self.discovery.start()
|
||||
}
|
||||
}
|
||||
@@ -164,29 +157,11 @@ final class GatewayConnectionController {
|
||||
self.discovery.setDebugLoggingEnabled(enabled)
|
||||
}
|
||||
|
||||
func requestLocalNetworkAccess(reason: String) {
|
||||
guard self.discoveryEnabled else {
|
||||
self.discovery.stop()
|
||||
self.updateFromDiscovery()
|
||||
return
|
||||
}
|
||||
|
||||
self.localNetworkAccessRequested = true
|
||||
GatewayDiagnostics.log("local network access requested reason=\(reason)")
|
||||
|
||||
guard self.currentScenePhase != .background else { return }
|
||||
self.discovery.start()
|
||||
self.updateFromDiscovery()
|
||||
self.attemptAutoReconnectIfNeeded()
|
||||
}
|
||||
|
||||
func setScenePhase(_ phase: ScenePhase) {
|
||||
self.currentScenePhase = phase
|
||||
guard self.discoveryEnabled else {
|
||||
self.discovery.stop()
|
||||
return
|
||||
}
|
||||
guard self.localNetworkAccessRequested else { return }
|
||||
|
||||
switch phase {
|
||||
case .background:
|
||||
@@ -206,10 +181,6 @@ final class GatewayConnectionController {
|
||||
self.updateFromDiscovery()
|
||||
return
|
||||
}
|
||||
guard self.localNetworkAccessRequested else {
|
||||
self.requestLocalNetworkAccess(reason: "restart_discovery")
|
||||
return
|
||||
}
|
||||
|
||||
self.discovery.stop()
|
||||
self.didAutoConnect = false
|
||||
@@ -226,7 +197,6 @@ final class GatewayConnectionController {
|
||||
_ gateway: GatewayDiscoveryModel.DiscoveredGateway,
|
||||
forceReconnect: Bool = false) async -> String?
|
||||
{
|
||||
self.requestLocalNetworkAccess(reason: "connect_discovered_gateway")
|
||||
let instanceId = UserDefaults.standard.string(forKey: "node.instanceId")?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if instanceId.isEmpty {
|
||||
@@ -305,7 +275,6 @@ final class GatewayConnectionController {
|
||||
authOverride: ManualAuthOverride? = nil,
|
||||
forceReconnect: Bool = false) async
|
||||
{
|
||||
self.requestLocalNetworkAccess(reason: "connect_manual")
|
||||
let instanceId = GatewaySettingsStore.currentInstanceID()
|
||||
let token =
|
||||
authOverride.map(\.token) ?? GatewaySettingsStore.loadGatewayToken(instanceId: instanceId)
|
||||
@@ -371,7 +340,6 @@ final class GatewayConnectionController {
|
||||
}
|
||||
|
||||
func connectLastKnown() async {
|
||||
self.requestLocalNetworkAccess(reason: "connect_last_known")
|
||||
guard let last = GatewaySettingsStore.loadLastGatewayConnection() else { return }
|
||||
switch last {
|
||||
case let .manual(host, port, useTLS, _):
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
|
||||
<string>OpenClaw uses your calendars to add events when you enable calendar access.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>OpenClaw uses the camera when you scan a Gateway setup QR code or ask your paired Gateway or assistant to capture a photo or short video from this iPhone, for example to connect to your Gateway or show your assistant a document, device screen, or workspace.</string>
|
||||
<string>OpenClaw can capture photos or short video clips when requested via the gateway.</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>OpenClaw uses your contacts so you can search and reference people while using the assistant.</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
|
||||
@@ -4103,9 +4103,6 @@ extension NodeAppModel {
|
||||
|
||||
private func registerAPNsTokenIfNeeded() async {
|
||||
let usesRelayTransport = await self.pushRegistrationManager.usesRelayTransport
|
||||
guard await self.canPublishAPNsRegistration(usesRelayTransport: usesRelayTransport) else {
|
||||
return
|
||||
}
|
||||
guard self.gatewayConnected else {
|
||||
if usesRelayTransport {
|
||||
GatewayDiagnostics.pushRelay.skipped("gateway_offline")
|
||||
@@ -4166,23 +4163,6 @@ extension NodeAppModel {
|
||||
}
|
||||
}
|
||||
|
||||
private func canPublishAPNsRegistration(usesRelayTransport: Bool) async -> Bool {
|
||||
guard PushEnrollmentConsent.disclosureAccepted else {
|
||||
if usesRelayTransport {
|
||||
GatewayDiagnostics.pushRelay.skipped("enrollment_disclosure_not_accepted")
|
||||
}
|
||||
return false
|
||||
}
|
||||
let status = await self.notificationAuthorizationStatus()
|
||||
guard Self.isNotificationAuthorizationAllowed(status) else {
|
||||
if usesRelayTransport {
|
||||
GatewayDiagnostics.pushRelay.skipped("notifications_not_authorized")
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func fetchPushRelayGatewayIdentity() async throws -> PushRelayGatewayIdentity {
|
||||
let response = try await self.operatorGateway.request(
|
||||
method: "gateway.identity.get",
|
||||
@@ -5146,10 +5126,6 @@ extension NodeAppModel {
|
||||
self.setOperatorConnected(connected)
|
||||
}
|
||||
|
||||
func _test_canPublishAPNsRegistration(usesRelayTransport: Bool = true) async -> Bool {
|
||||
await self.canPublishAPNsRegistration(usesRelayTransport: usesRelayTransport)
|
||||
}
|
||||
|
||||
nonisolated static func _test_makeWatchChatItems(from raw: [OpenClawKit.AnyCodable]) -> [OpenClawWatchChatItem] {
|
||||
self.makeWatchChatItems(from: raw)
|
||||
}
|
||||
|
||||
@@ -73,16 +73,10 @@ struct OnboardingWizardView: View {
|
||||
private static let pairingAutoResumeTicker = Timer.publish(every: 2.0, on: .main, in: .common).autoconnect()
|
||||
|
||||
let allowSkip: Bool
|
||||
let onRequestLocalNetworkAccess: (String) -> Void
|
||||
let onClose: () -> Void
|
||||
|
||||
init(
|
||||
allowSkip: Bool,
|
||||
onRequestLocalNetworkAccess: @escaping (String) -> Void,
|
||||
onClose: @escaping () -> Void)
|
||||
{
|
||||
init(allowSkip: Bool, onClose: @escaping () -> Void) {
|
||||
self.allowSkip = allowSkip
|
||||
self.onRequestLocalNetworkAccess = onRequestLocalNetworkAccess
|
||||
self.onClose = onClose
|
||||
_step = State(
|
||||
initialValue: OnboardingStateStore.shouldPresentFirstRunIntro() ? .intro : .welcome)
|
||||
@@ -237,7 +231,6 @@ struct OnboardingWizardView: View {
|
||||
}
|
||||
.onAppear {
|
||||
self.initializeState()
|
||||
self.requestLocalNetworkAccessIfPastIntro(reason: "onboarding_appear")
|
||||
}
|
||||
.onDisappear {
|
||||
self.discoveryRestartTask?.cancel()
|
||||
@@ -871,20 +864,10 @@ extension OnboardingWizardView {
|
||||
|
||||
private func advanceFromIntro() {
|
||||
OnboardingStateStore.markFirstRunIntroSeen()
|
||||
self.requestLocalNetworkAccess(reason: "onboarding_continue")
|
||||
self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here."
|
||||
self.step = .welcome
|
||||
}
|
||||
|
||||
private func requestLocalNetworkAccessIfPastIntro(reason: String) {
|
||||
guard self.step != .intro else { return }
|
||||
self.requestLocalNetworkAccess(reason: reason)
|
||||
}
|
||||
|
||||
private func requestLocalNetworkAccess(reason: String) {
|
||||
self.onRequestLocalNetworkAccess(reason)
|
||||
}
|
||||
|
||||
private func navigateBack() {
|
||||
guard let target = self.step.previous else { return }
|
||||
self.connectingGatewayID = nil
|
||||
|
||||
@@ -123,28 +123,8 @@ final class OpenClawAppDelegate: NSObject, UIApplicationDelegate, @preconcurrenc
|
||||
let notificationCenter = UNUserNotificationCenter.current()
|
||||
notificationCenter.delegate = self
|
||||
ExecApprovalNotificationBridge.registerCategory(center: notificationCenter)
|
||||
Task { @MainActor in
|
||||
await self.registerForRemoteNotificationsIfEnrollmentReady(application)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func registerForRemoteNotificationsIfEnrollmentReady(_ application: UIApplication) async {
|
||||
guard PushEnrollmentConsent.disclosureAccepted else { return }
|
||||
guard await Self.isNotificationAuthorizationAllowed() else { return }
|
||||
application.registerForRemoteNotifications()
|
||||
}
|
||||
|
||||
private static func isNotificationAuthorizationAllowed() async -> Bool {
|
||||
let settings = await UNUserNotificationCenter.current().notificationSettings()
|
||||
switch settings.authorizationStatus {
|
||||
case .authorized, .provisional, .ephemeral:
|
||||
return true
|
||||
case .denied, .notDetermined:
|
||||
return false
|
||||
@unknown default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
@@ -646,8 +626,7 @@ struct OpenClawApp: App {
|
||||
_gatewayController = State(
|
||||
initialValue: GatewayConnectionController(
|
||||
appModel: appModel,
|
||||
startDiscovery: !Self.screenshotModeEnabled,
|
||||
deferDiscoveryUntilLocalNetworkRequest: true))
|
||||
startDiscovery: !Self.screenshotModeEnabled))
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
enum PushEnrollmentConsent {
|
||||
static let disclosureAcceptedKey = "push.enrollment.disclosureAccepted"
|
||||
|
||||
static var disclosureAccepted: Bool {
|
||||
UserDefaults.standard.bool(forKey: disclosureAcceptedKey)
|
||||
}
|
||||
|
||||
static func markDisclosureAccepted() {
|
||||
UserDefaults.standard.set(true, forKey: self.disclosureAcceptedKey)
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
static func reset() {
|
||||
UserDefaults.standard.removeObject(forKey: self.disclosureAcceptedKey)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -683,7 +683,6 @@ struct RootTabs: View {
|
||||
self.updateIdleTimer()
|
||||
self.updateHomeCanvasState()
|
||||
guard newValue == .active else { return }
|
||||
self.maybeRequestLocalNetworkAccess(reason: "scene_active")
|
||||
Task {
|
||||
await self.appModel.refreshGatewayOverviewIfConnected()
|
||||
await MainActor.run {
|
||||
@@ -730,10 +729,6 @@ struct RootTabs: View {
|
||||
.onChange(of: self.onboardingRequestID) { _, _ in
|
||||
self.evaluateOnboardingPresentation(force: true)
|
||||
}
|
||||
.onChange(of: self.showOnboarding) { _, newValue in
|
||||
guard !newValue else { return }
|
||||
self.maybeRequestLocalNetworkAccess(reason: "onboarding_dismissed")
|
||||
}
|
||||
.onChange(of: self.appModel.openChatRequestID) { _, _ in
|
||||
self.selectSidebarDestination(.chat)
|
||||
}
|
||||
@@ -772,9 +767,6 @@ struct RootTabs: View {
|
||||
.fullScreenCover(isPresented: self.$showOnboarding) {
|
||||
OnboardingWizardView(
|
||||
allowSkip: self.onboardingAllowSkip,
|
||||
onRequestLocalNetworkAccess: { reason in
|
||||
self.requestLocalNetworkAccess(reason: reason)
|
||||
},
|
||||
onClose: {
|
||||
self.showOnboarding = false
|
||||
})
|
||||
@@ -1053,14 +1045,13 @@ extension RootTabs {
|
||||
shouldPresentOnLaunch: OnboardingStateStore.shouldPresentOnLaunch(appModel: self.appModel))
|
||||
switch route {
|
||||
case .none:
|
||||
self.maybeRequestLocalNetworkAccess(reason: "root_appear")
|
||||
break
|
||||
case .onboarding:
|
||||
self.onboardingAllowSkip = true
|
||||
self.showOnboarding = true
|
||||
case .settings:
|
||||
self.didAutoOpenSettings = true
|
||||
self.selectSidebarDestination(.gateway)
|
||||
self.maybeRequestLocalNetworkAccess(reason: "root_appear")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1087,7 +1078,6 @@ extension RootTabs {
|
||||
guard route == .settings else { return }
|
||||
self.didAutoOpenSettings = true
|
||||
self.selectSidebarDestination(.gateway)
|
||||
self.maybeRequestLocalNetworkAccess(reason: "auto_open_settings")
|
||||
}
|
||||
|
||||
private func maybeOpenSettingsForGatewaySetup() {
|
||||
@@ -1098,19 +1088,6 @@ extension RootTabs {
|
||||
self.presentedSheet = nil
|
||||
self.didAutoOpenSettings = true
|
||||
self.selectSidebarDestination(.gateway)
|
||||
self.requestLocalNetworkAccess(reason: "gateway_setup_deeplink")
|
||||
}
|
||||
|
||||
private func maybeRequestLocalNetworkAccess(reason: String) {
|
||||
guard self.didEvaluateOnboarding else { return }
|
||||
guard self.scenePhase == .active else { return }
|
||||
guard !self.showOnboarding else { return }
|
||||
self.requestLocalNetworkAccess(reason: reason)
|
||||
}
|
||||
|
||||
private func requestLocalNetworkAccess(reason: String) {
|
||||
guard !self.appModel.isAppleReviewDemoModeEnabled else { return }
|
||||
self.gatewayController.requestLocalNetworkAccess(reason: reason)
|
||||
}
|
||||
|
||||
private func applyInitialChatSessionIfNeeded() {
|
||||
|
||||
@@ -76,7 +76,6 @@ Sources/Permissions/PermissionRequestBridge.swift
|
||||
Sources/Push/ExecApprovalNotificationBridge.swift
|
||||
Sources/Push/BackgroundAliveBeacon.swift
|
||||
Sources/Push/PushBuildConfig.swift
|
||||
Sources/Push/PushEnrollmentConsent.swift
|
||||
Sources/Push/PushRegistrationManager.swift
|
||||
Sources/Push/PushRelayClient.swift
|
||||
Sources/Push/PushRelayKeychainStore.swift
|
||||
|
||||
@@ -1377,24 +1377,6 @@ private final class MockBootstrapNotificationCenter: NotificationCentering, @unc
|
||||
#expect(center.addCalls == 1)
|
||||
}
|
||||
|
||||
@Test @MainActor func apnsRegistrationRequiresDisclosureAndNotificationAuthorization() async {
|
||||
let center = MockBootstrapNotificationCenter()
|
||||
center.status = .authorized
|
||||
let appModel = NodeAppModel(notificationCenter: center)
|
||||
PushEnrollmentConsent.reset()
|
||||
defer { PushEnrollmentConsent.reset() }
|
||||
|
||||
#expect(await appModel._test_canPublishAPNsRegistration() == false)
|
||||
#expect(await appModel._test_canPublishAPNsRegistration(usesRelayTransport: false) == false)
|
||||
|
||||
PushEnrollmentConsent.markDisclosureAccepted()
|
||||
center.status = .notDetermined
|
||||
#expect(await appModel._test_canPublishAPNsRegistration() == false)
|
||||
|
||||
center.status = .authorized
|
||||
#expect(await appModel._test_canPublishAPNsRegistration())
|
||||
}
|
||||
|
||||
@Test @MainActor func chatPushWithoutSpeechReturnsUnavailableWhenNotificationsOff() async throws {
|
||||
let center = MockBootstrapNotificationCenter()
|
||||
center.status = .notDetermined
|
||||
|
||||
@@ -550,20 +550,6 @@ struct RootTabsSourceGuardTests {
|
||||
#expect(docsSource.contains(".accessibilityHint(\"Opens Settings / Gateway\")"))
|
||||
}
|
||||
|
||||
@Test func `push enrollment stays behind notification disclosure flow`() throws {
|
||||
let appSource = try String(contentsOf: Self.openClawAppSourceURL(), encoding: .utf8)
|
||||
let actionsSource = try String(contentsOf: Self.settingsProTabActionsSourceURL(), encoding: .utf8)
|
||||
let modelSource = try String(contentsOf: Self.nodeAppModelSourceURL(), encoding: .utf8)
|
||||
|
||||
#expect(appSource.contains("PushEnrollmentConsent.disclosureAccepted"))
|
||||
#expect(appSource.contains("await Self.isNotificationAuthorizationAllowed()"))
|
||||
#expect(actionsSource.contains("PushEnrollmentConsent.markDisclosureAccepted()"))
|
||||
#expect(actionsSource.contains("self.registerForRemoteNotificationsIfEnrollmentReady()"))
|
||||
#expect(modelSource.contains("PushEnrollmentConsent.disclosureAccepted"))
|
||||
#expect(modelSource.contains("notifications_not_authorized"))
|
||||
#expect(modelSource.contains("enrollment_disclosure_not_accepted"))
|
||||
}
|
||||
|
||||
@Test func `gateway settings keeps pairing trust diagnostics and tailscale actions`() throws {
|
||||
let settingsSource = try String(contentsOf: Self.settingsProTabSourceURL(), encoding: .utf8)
|
||||
let sectionsSource = try String(contentsOf: Self.settingsProTabSectionsSourceURL(), encoding: .utf8)
|
||||
@@ -594,7 +580,6 @@ struct RootTabsSourceGuardTests {
|
||||
#expect(actionsSource.contains("self.gatewayController.refreshActiveGatewayRegistrationFromSettings()"))
|
||||
#expect(actionsSource.contains("self.gatewayController.restartDiscovery()"))
|
||||
#expect(actionsSource.contains("await self.appModel.refreshGatewayOverviewIfConnected()"))
|
||||
#expect(actionsSource.contains("self.gatewayController.requestLocalNetworkAccess(reason: \"settings_preflight\")"))
|
||||
#expect(actionsSource.contains("await TCPProbe.probe(host: trimmed, port: port"))
|
||||
#expect(actionsSource.contains("Check Tailscale or LAN."))
|
||||
#expect(actionsSource.contains("Tailscale is off on this device. Turn it on, then try again."))
|
||||
@@ -611,32 +596,6 @@ struct RootTabsSourceGuardTests {
|
||||
#expect(controllerSource.contains("trustRotatedGatewayCertificate(from problem: GatewayConnectionProblem)"))
|
||||
}
|
||||
|
||||
@Test func `local network access is requested from visible gateway flows`() throws {
|
||||
let appSource = try String(contentsOf: Self.openClawAppSourceURL(), encoding: .utf8)
|
||||
let rootSource = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
|
||||
let onboardingSource = try String(contentsOf: Self.onboardingWizardSourceURL(), encoding: .utf8)
|
||||
let actionsSource = try String(contentsOf: Self.settingsProTabActionsSourceURL(), encoding: .utf8)
|
||||
let controllerSource = try String(contentsOf: Self.gatewayConnectionControllerSourceURL(), encoding: .utf8)
|
||||
|
||||
#expect(appSource.contains("deferDiscoveryUntilLocalNetworkRequest: true"))
|
||||
#expect(controllerSource.contains("func requestLocalNetworkAccess(reason: String)"))
|
||||
#expect(controllerSource.contains("guard self.localNetworkAccessRequested else"))
|
||||
#expect(controllerSource.contains("self.requestLocalNetworkAccess(reason: \"connect_manual\")"))
|
||||
#expect(controllerSource.contains("self.requestLocalNetworkAccess(reason: \"connect_discovered_gateway\")"))
|
||||
#expect(controllerSource.contains("self.requestLocalNetworkAccess(reason: \"connect_last_known\")"))
|
||||
|
||||
#expect(rootSource.contains("self.maybeRequestLocalNetworkAccess(reason: \"root_appear\")"))
|
||||
#expect(rootSource.contains("self.maybeRequestLocalNetworkAccess(reason: \"scene_active\")"))
|
||||
#expect(rootSource.contains("self.maybeRequestLocalNetworkAccess(reason: \"onboarding_dismissed\")"))
|
||||
#expect(rootSource.contains("self.requestLocalNetworkAccess(reason: \"gateway_setup_deeplink\")"))
|
||||
#expect(rootSource.contains("guard self.didEvaluateOnboarding else { return }"))
|
||||
#expect(rootSource.contains("onRequestLocalNetworkAccess: { reason in"))
|
||||
|
||||
#expect(onboardingSource.contains("self.requestLocalNetworkAccess(reason: \"onboarding_continue\")"))
|
||||
#expect(onboardingSource.contains("self.requestLocalNetworkAccessIfPastIntro(reason: \"onboarding_appear\")"))
|
||||
#expect(actionsSource.contains("self.gatewayController.requestLocalNetworkAccess(reason: \"settings_preflight\")"))
|
||||
}
|
||||
|
||||
@Test func `gateway settings preview matrix covers primary states`() throws {
|
||||
let supportSource = try String(contentsOf: Self.settingsProTabSupportSourceURL(), encoding: .utf8)
|
||||
|
||||
@@ -827,20 +786,6 @@ struct RootTabsSourceGuardTests {
|
||||
.appendingPathComponent("Sources/Design/SettingsProTab.swift")
|
||||
}
|
||||
|
||||
private static func onboardingWizardSourceURL() -> URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appendingPathComponent("Sources/Onboarding/OnboardingWizardView.swift")
|
||||
}
|
||||
|
||||
private static func openClawAppSourceURL() -> URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appendingPathComponent("Sources/OpenClawApp.swift")
|
||||
}
|
||||
|
||||
private static func notificationPermissionGuidanceDialogSourceURL() -> URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
|
||||
@@ -96,7 +96,7 @@ Pinned iOS version `2026.4.10` maps to:
|
||||
- creates or verifies Developer Portal bundle IDs/services through Fastlane `produce`
|
||||
- syncs encrypted App Store signing assets with Fastlane `match`
|
||||
- increments App Store Connect build numbers for the pinned short version
|
||||
- uploads screenshots, release notes, and the rendered App Review PDF attachment before archiving a release build
|
||||
- uploads screenshots and release notes before archiving a release build
|
||||
|
||||
## Release-note resolution order
|
||||
|
||||
@@ -129,37 +129,6 @@ pnpm ios:version:pin -- --version 2026.4.10
|
||||
|
||||
This keeps the TestFlight version stable while review is in flight.
|
||||
|
||||
## Release SHA tracking
|
||||
|
||||
Successful App Store Connect uploads create a non-tag Git ref that records the
|
||||
source commit for the uploaded store build:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/ios/<CFBundleShortVersionString>-<CFBundleVersion>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/ios/2026.6.10-8
|
||||
```
|
||||
|
||||
These refs are intentionally outside `refs/tags/*` and `refs/heads/*`. They do
|
||||
not appear on GitHub release or tag pages, and they do not participate in the
|
||||
core OpenClaw release machinery.
|
||||
|
||||
`pnpm ios:release:upload` checks the ref before archive/upload work and records
|
||||
it only after `upload_to_testflight` succeeds. Existing refs are immutable: the
|
||||
same ref at the same SHA is accepted, while the same ref at a different SHA
|
||||
fails.
|
||||
|
||||
Useful direct commands:
|
||||
|
||||
```bash
|
||||
pnpm mobile:release:preflight -- --platform ios --version 2026.6.10 --build 8
|
||||
pnpm mobile:release:resolve -- --platform ios --version 2026.6.10 --build 8
|
||||
```
|
||||
|
||||
## New release promotion workflow
|
||||
|
||||
When you want the next production iOS release to align with the current gateway release:
|
||||
@@ -187,4 +156,4 @@ Fastlane and Xcode should consume only the pinned iOS version from `apps/ios/ver
|
||||
|
||||
Changing `package.json.version` alone must not change the iOS app version until a maintainer explicitly runs the pin step.
|
||||
|
||||
App Review submission must remain manual. Automation may create/update the editable App Store version, upload screenshots, upload release notes, upload the App Review PDF attachment, and upload builds, but it should not upload the App Store Connect `Notes` field or submit a build for review.
|
||||
App Review submission must remain manual. Automation may create/update the editable App Store version, upload screenshots, upload release notes, and upload builds, but it should not submit a build for review.
|
||||
|
||||
@@ -5,7 +5,6 @@ require "fileutils"
|
||||
require "tmpdir"
|
||||
require "tempfile"
|
||||
require "cgi"
|
||||
require "digest/md5"
|
||||
|
||||
default_platform(:ios)
|
||||
|
||||
@@ -48,14 +47,6 @@ PUBLIC_METADATA_FILENAMES = [
|
||||
"subtitle.txt",
|
||||
"support_url.txt"
|
||||
].freeze
|
||||
APP_REVIEW_NOTES_METADATA_FILENAMES = [
|
||||
"notes.txt",
|
||||
"review_notes.txt"
|
||||
].freeze
|
||||
APP_STORE_SCREENSHOT_LIMIT_PER_SET = 10
|
||||
APP_STORE_SCREENSHOT_SET_DELETE_TIMEOUT_SECONDS = 120
|
||||
APP_STORE_SCREENSHOT_PROCESSING_TIMEOUT_SECONDS = 3600
|
||||
APP_STORE_SCREENSHOT_PROCESSING_POLL_SECONDS = 5
|
||||
|
||||
def load_env_file(path)
|
||||
return unless File.exist?(path)
|
||||
@@ -88,6 +79,10 @@ def release_notes_upload_requested?
|
||||
ENV["DELIVER_RELEASE_NOTES"] == "1"
|
||||
end
|
||||
|
||||
def screenshot_paths
|
||||
Dir[File.join(__dir__, "screenshots", "**", "*.png")]
|
||||
end
|
||||
|
||||
def validate_required_screenshots!(paths)
|
||||
missing_families = REQUIRED_SCREENSHOT_FAMILIES.filter_map do |name, pattern|
|
||||
name unless paths.any? { |path| File.basename(path).match?(pattern) }
|
||||
@@ -289,7 +284,6 @@ def normalize_watch_screenshot_status_bar(path)
|
||||
script = <<~SWIFT
|
||||
import AppKit
|
||||
import Foundation
|
||||
import ImageIO
|
||||
|
||||
let path = CommandLine.arguments[1]
|
||||
let timeText = CommandLine.arguments[2]
|
||||
@@ -301,37 +295,36 @@ def normalize_watch_screenshot_status_bar(path)
|
||||
exit(2)
|
||||
}
|
||||
|
||||
let width = cgImage.width
|
||||
let height = cgImage.height
|
||||
let drawWidth = CGFloat(width)
|
||||
let drawHeight = CGFloat(height)
|
||||
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
|
||||
guard let bitmapContext = CGContext(
|
||||
data: nil,
|
||||
width: width,
|
||||
height: height,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: width * 4,
|
||||
space: colorSpace,
|
||||
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue)
|
||||
let width = CGFloat(cgImage.width)
|
||||
let height = CGFloat(cgImage.height)
|
||||
guard let bitmap = NSBitmapImageRep(
|
||||
bitmapDataPlanes: nil,
|
||||
pixelsWide: Int(width),
|
||||
pixelsHigh: Int(height),
|
||||
bitsPerSample: 8,
|
||||
samplesPerPixel: 4,
|
||||
hasAlpha: true,
|
||||
isPlanar: false,
|
||||
colorSpaceName: .deviceRGB,
|
||||
bytesPerRow: 0,
|
||||
bitsPerPixel: 0),
|
||||
let graphicsContext = NSGraphicsContext(bitmapImageRep: bitmap)
|
||||
else {
|
||||
fputs("Failed to create normalized screenshot bitmap at \\(path)\\n", stderr)
|
||||
exit(3)
|
||||
}
|
||||
|
||||
let graphicsContext = NSGraphicsContext(cgContext: bitmapContext, flipped: false)
|
||||
bitmap.size = NSSize(width: width, height: height)
|
||||
NSGraphicsContext.saveGraphicsState()
|
||||
NSGraphicsContext.current = graphicsContext
|
||||
NSColor.black.setFill()
|
||||
NSBezierPath(rect: NSRect(x: 0, y: 0, width: drawWidth, height: drawHeight)).fill()
|
||||
source.draw(
|
||||
in: NSRect(x: 0, y: 0, width: drawWidth, height: drawHeight),
|
||||
from: NSRect(x: 0, y: 0, width: drawWidth, height: drawHeight),
|
||||
operation: .sourceOver,
|
||||
in: NSRect(x: 0, y: 0, width: width, height: height),
|
||||
from: NSRect(x: 0, y: 0, width: width, height: height),
|
||||
operation: .copy,
|
||||
fraction: 1.0)
|
||||
|
||||
NSColor.black.setFill()
|
||||
NSBezierPath(rect: NSRect(x: drawWidth - 146, y: drawHeight - 92, width: 124, height: 70)).fill()
|
||||
NSBezierPath(rect: NSRect(x: width - 146, y: height - 92, width: 124, height: 70)).fill()
|
||||
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.alignment = .right
|
||||
@@ -341,26 +334,17 @@ def normalize_watch_screenshot_status_bar(path)
|
||||
.paragraphStyle: paragraphStyle,
|
||||
]
|
||||
timeText.draw(
|
||||
in: NSRect(x: drawWidth - 134, y: drawHeight - 82, width: 102, height: 44),
|
||||
in: NSRect(x: width - 134, y: height - 82, width: 102, height: 44),
|
||||
withAttributes: attributes)
|
||||
NSGraphicsContext.restoreGraphicsState()
|
||||
|
||||
guard let output = bitmapContext.makeImage(),
|
||||
let destination = CGImageDestinationCreateWithURL(
|
||||
URL(fileURLWithPath: path) as CFURL,
|
||||
"public.png" as CFString,
|
||||
1,
|
||||
nil)
|
||||
guard let png = bitmap.representation(using: .png, properties: [:])
|
||||
else {
|
||||
fputs("Failed to encode normalized screenshot at \\(path)\\n", stderr)
|
||||
exit(4)
|
||||
}
|
||||
|
||||
CGImageDestinationAddImage(destination, output, nil)
|
||||
guard CGImageDestinationFinalize(destination) else {
|
||||
fputs("Failed to write normalized screenshot at \\(path)\\n", stderr)
|
||||
exit(5)
|
||||
}
|
||||
try png.write(to: URL(fileURLWithPath: path))
|
||||
SWIFT
|
||||
|
||||
Tempfile.create(["openclaw-watch-status-bar", ".swift"]) do |file|
|
||||
@@ -737,37 +721,6 @@ def release_notes_metadata_path
|
||||
temp_root
|
||||
end
|
||||
|
||||
def app_review_notes_markdown_path
|
||||
File.join(ios_root, "APP-REVIEW-NOTES.md")
|
||||
end
|
||||
|
||||
def app_review_notes_pdf_path
|
||||
File.join(ios_root, "build", "app-review", "APP-REVIEW-NOTES.pdf")
|
||||
end
|
||||
|
||||
def generate_app_review_notes_pdf!
|
||||
source = app_review_notes_markdown_path
|
||||
UI.user_error!("Missing App Review notes at #{source}.") unless File.exist?(source)
|
||||
|
||||
output = app_review_notes_pdf_path
|
||||
FileUtils.mkdir_p(File.dirname(output))
|
||||
sh(shell_join(["xcrun", "swift", File.join(repo_root, "scripts", "ios-app-review-notes-pdf.swift"), source, output]))
|
||||
output
|
||||
end
|
||||
|
||||
def assert_no_app_review_notes_field_metadata!(metadata_path)
|
||||
notes_dir = File.join(metadata_path, "review_information")
|
||||
APP_REVIEW_NOTES_METADATA_FILENAMES.each do |filename|
|
||||
path = File.join(notes_dir, filename)
|
||||
next unless File.exist?(path)
|
||||
|
||||
UI.user_error!(
|
||||
"Refusing to upload App Review Notes metadata from #{path}. " \
|
||||
"Maintain the App Store Connect Notes field manually so the live setup code is not stored in this repo."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def public_metadata_path
|
||||
source = File.join(__dir__, "metadata")
|
||||
temp_root = Dir.mktmpdir("openclaw-app-store-metadata")
|
||||
@@ -781,259 +734,6 @@ def public_metadata_path
|
||||
temp_root
|
||||
end
|
||||
|
||||
def app_store_screenshot_root
|
||||
File.join(__dir__, "screenshots")
|
||||
end
|
||||
|
||||
def app_store_screenshot_manifest
|
||||
require "deliver/loader"
|
||||
|
||||
Deliver::Loader.load_app_screenshots(app_store_screenshot_root, false)
|
||||
end
|
||||
|
||||
def resolve_app_store_connect_app(app_identifier:, app_id:)
|
||||
require "spaceship"
|
||||
|
||||
app = if env_present?(app_id) && !env_present?(app_identifier)
|
||||
Spaceship::ConnectAPI::App.get(app_id: app_id)
|
||||
else
|
||||
Spaceship::ConnectAPI::App.find(app_identifier || APP_STORE_APP_IDENTIFIER)
|
||||
end
|
||||
UI.user_error!("Could not find App Store Connect app #{app_identifier || app_id || APP_STORE_APP_IDENTIFIER}.") unless app
|
||||
app
|
||||
end
|
||||
|
||||
def resolve_app_store_connect_version(app:, short_version:)
|
||||
version = app.get_edit_app_store_version(platform: Spaceship::ConnectAPI::Platform::IOS)
|
||||
UI.user_error!("Could not find an editable App Store Connect version for #{app.name}.") unless version
|
||||
if version.version_string != short_version
|
||||
UI.user_error!(
|
||||
"Editable App Store Connect version mismatch for #{app.name}: expected #{short_version}, got #{version.version_string}."
|
||||
)
|
||||
end
|
||||
version
|
||||
end
|
||||
|
||||
def app_store_screenshot_sets_for_display_type(localization:, display_type:)
|
||||
localization
|
||||
.get_app_screenshot_sets(includes: "appScreenshots")
|
||||
.select { |set| set.screenshot_display_type == display_type }
|
||||
end
|
||||
|
||||
def clear_app_store_screenshot_sets!(localization:)
|
||||
existing_sets = localization.get_app_screenshot_sets(includes: "appScreenshots")
|
||||
return if existing_sets.empty?
|
||||
|
||||
existing_sets.each do |set|
|
||||
UI.message("Deleting existing #{localization.locale} #{set.screenshot_display_type} screenshot set #{set.id}.")
|
||||
set.delete!
|
||||
end
|
||||
|
||||
deadline = Time.now + APP_STORE_SCREENSHOT_SET_DELETE_TIMEOUT_SECONDS
|
||||
loop do
|
||||
sets = localization.get_app_screenshot_sets(includes: "appScreenshots")
|
||||
return if sets.empty?
|
||||
|
||||
if Time.now >= deadline
|
||||
UI.user_error!(
|
||||
"Timed out waiting for App Store Connect to delete #{localization.locale} screenshot sets: #{sets.map(&:id).join(', ')}."
|
||||
)
|
||||
end
|
||||
sleep(3)
|
||||
end
|
||||
end
|
||||
|
||||
def app_store_screenshot_expected_rows(screenshots)
|
||||
screenshots.map do |screenshot|
|
||||
{
|
||||
checksum: Digest::MD5.file(screenshot.path).hexdigest,
|
||||
file_name: File.basename(screenshot.path)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def app_store_screenshot_actual_rows(app_screenshot_set)
|
||||
(app_screenshot_set.app_screenshots || []).map do |screenshot|
|
||||
{
|
||||
checksum: screenshot.source_file_checksum,
|
||||
file_name: screenshot.file_name,
|
||||
state: (screenshot.asset_delivery_state || {})["state"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def format_app_store_screenshot_rows(rows)
|
||||
rows.map do |row|
|
||||
[row[:file_name], row[:checksum], row[:state]].compact.join(" ")
|
||||
end.join(", ")
|
||||
end
|
||||
|
||||
def app_store_screenshot_processing_timeout_seconds
|
||||
raw = ENV["DELIVER_SCREENSHOT_PROCESSING_TIMEOUT"].to_s.strip
|
||||
return APP_STORE_SCREENSHOT_PROCESSING_TIMEOUT_SECONDS if raw.empty?
|
||||
|
||||
unless raw.match?(/\A\d+\z/) && raw.to_i.positive?
|
||||
UI.user_error!("Invalid DELIVER_SCREENSHOT_PROCESSING_TIMEOUT '#{raw}'. Expected a positive number of seconds.")
|
||||
end
|
||||
raw.to_i
|
||||
end
|
||||
|
||||
def app_store_screenshot_state_counts(screenshots)
|
||||
screenshots.each_with_object({}) do |screenshot, counts|
|
||||
state = (screenshot.asset_delivery_state || {})["state"] || "UNKNOWN"
|
||||
counts[state] ||= 0
|
||||
counts[state] += 1
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_app_store_screenshots_processing!(screenshot_ids:, locale:, display_type:)
|
||||
timeout_seconds = app_store_screenshot_processing_timeout_seconds
|
||||
deadline = Time.now + timeout_seconds
|
||||
loop do
|
||||
screenshots = screenshot_ids.map do |screenshot_id|
|
||||
Spaceship::ConnectAPI.get_app_screenshot(app_screenshot_id: screenshot_id).first
|
||||
end
|
||||
|
||||
failed = screenshots.select(&:error?)
|
||||
unless failed.empty?
|
||||
details = failed.map { |screenshot| "#{screenshot.file_name}: #{screenshot.error_messages.join(', ')}" }
|
||||
UI.user_error!("App Store Connect failed processing #{locale} #{display_type} screenshots: #{details.join('; ')}.")
|
||||
end
|
||||
return screenshots if screenshots.all?(&:complete?)
|
||||
|
||||
if Time.now >= deadline
|
||||
states = app_store_screenshot_state_counts(screenshots)
|
||||
UI.user_error!(
|
||||
"Timed out after #{timeout_seconds}s waiting for App Store Connect to process #{locale} #{display_type} screenshots: #{states}."
|
||||
)
|
||||
end
|
||||
|
||||
UI.verbose("Waiting for #{locale} #{display_type} screenshots to finish processing: #{app_store_screenshot_state_counts(screenshots)}.")
|
||||
sleep(APP_STORE_SCREENSHOT_PROCESSING_POLL_SECONDS)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_app_store_screenshot_target_counts!(screenshots_by_target)
|
||||
screenshots_by_target.each do |(locale, display_type), screenshots|
|
||||
next if screenshots.length <= APP_STORE_SCREENSHOT_LIMIT_PER_SET
|
||||
|
||||
UI.user_error!(
|
||||
"Found #{screenshots.length} screenshots for #{locale} #{display_type}; App Store Connect allows #{APP_STORE_SCREENSHOT_LIMIT_PER_SET}."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def verify_app_store_screenshot_set!(app_screenshot_set:, screenshots:, locale:, display_type:)
|
||||
expected = app_store_screenshot_expected_rows(screenshots)
|
||||
timeout_seconds = app_store_screenshot_processing_timeout_seconds
|
||||
deadline = Time.now + timeout_seconds
|
||||
actual = []
|
||||
|
||||
loop do
|
||||
app_screenshot_set = Spaceship::ConnectAPI::AppScreenshotSet.get(app_screenshot_set_id: app_screenshot_set.id)
|
||||
actual = app_store_screenshot_actual_rows(app_screenshot_set)
|
||||
actual_identity = actual.map { |row| { checksum: row[:checksum], file_name: row[:file_name] } }
|
||||
incomplete = actual.reject { |row| row[:state] == "COMPLETE" }
|
||||
|
||||
return if actual_identity == expected && incomplete.empty?
|
||||
|
||||
if actual.length > expected.length
|
||||
UI.user_error!(
|
||||
"App Store Connect screenshot verification failed for #{locale} #{display_type}. " \
|
||||
"Expected: #{format_app_store_screenshot_rows(expected)}. " \
|
||||
"Actual: #{format_app_store_screenshot_rows(actual)}."
|
||||
)
|
||||
end
|
||||
|
||||
if Time.now >= deadline
|
||||
UI.user_error!(
|
||||
"Timed out after #{timeout_seconds}s waiting for App Store Connect screenshot verification for #{locale} #{display_type}. " \
|
||||
"Expected: #{format_app_store_screenshot_rows(expected)}. " \
|
||||
"Actual: #{format_app_store_screenshot_rows(actual)}."
|
||||
)
|
||||
end
|
||||
|
||||
UI.verbose(
|
||||
"Waiting for App Store Connect screenshot verification for #{locale} #{display_type}: " \
|
||||
"#{format_app_store_screenshot_rows(actual)}."
|
||||
)
|
||||
sleep(APP_STORE_SCREENSHOT_PROCESSING_POLL_SECONDS)
|
||||
end
|
||||
end
|
||||
|
||||
def replace_app_store_screenshot_set!(localization:, display_type:, screenshots:)
|
||||
existing_sets = app_store_screenshot_sets_for_display_type(localization: localization, display_type: display_type)
|
||||
unless existing_sets.empty?
|
||||
UI.user_error!(
|
||||
"App Store Connect still has #{localization.locale} #{display_type} screenshot sets after reset: #{existing_sets.map(&:id).join(', ')}."
|
||||
)
|
||||
end
|
||||
|
||||
UI.message("Creating #{localization.locale} #{display_type} screenshot set.")
|
||||
app_screenshot_set = localization.create_app_screenshot_set(attributes: { screenshotDisplayType: display_type })
|
||||
uploaded_ids = screenshots.map.with_index do |screenshot, index|
|
||||
started_at = Time.now
|
||||
uploaded = app_screenshot_set.upload_screenshot(path: screenshot.path, wait_for_processing: false)
|
||||
UI.message(
|
||||
"Uploaded #{localization.locale} #{display_type} screenshot #{index + 1}/#{screenshots.length}: " \
|
||||
"#{File.basename(screenshot.path)} (#{(Time.now - started_at).round(1)}s)."
|
||||
)
|
||||
uploaded.id
|
||||
end
|
||||
wait_for_app_store_screenshots_processing!(
|
||||
screenshot_ids: uploaded_ids,
|
||||
locale: localization.locale,
|
||||
display_type: display_type
|
||||
)
|
||||
|
||||
app_screenshot_set = Spaceship::ConnectAPI::AppScreenshotSet.get(app_screenshot_set_id: app_screenshot_set.id)
|
||||
app_screenshot_set = app_screenshot_set.reorder_screenshots(app_screenshot_ids: uploaded_ids)
|
||||
verify_app_store_screenshot_set!(
|
||||
app_screenshot_set: app_screenshot_set,
|
||||
screenshots: screenshots,
|
||||
locale: localization.locale,
|
||||
display_type: display_type
|
||||
)
|
||||
end
|
||||
|
||||
# Fastlane deliver can duplicate complete screenshots when its verification retry
|
||||
# runs before App Store Connect consistently lists processed assets. Keep the
|
||||
# screenshot write path serial and assert the remote set equals the local files.
|
||||
def upload_app_store_screenshots_deterministically!(app_identifier:, app_id:, short_version:, screenshots:)
|
||||
app = resolve_app_store_connect_app(app_identifier: app_identifier, app_id: app_id)
|
||||
version = resolve_app_store_connect_version(app: app, short_version: short_version)
|
||||
localizations_by_locale = version.get_app_store_version_localizations.each_with_object({}) do |localization, index|
|
||||
index[localization.locale] = localization
|
||||
end
|
||||
|
||||
screenshots_by_target = screenshots
|
||||
.sort_by { |screenshot| [screenshot.language.to_s, screenshot.display_type.to_s, File.basename(screenshot.path)] }
|
||||
.group_by { |screenshot| [screenshot.language, screenshot.display_type] }
|
||||
validate_app_store_screenshot_target_counts!(screenshots_by_target)
|
||||
|
||||
missing_locales = screenshots_by_target.keys.map(&:first).uniq.reject { |locale| localizations_by_locale.key?(locale) }
|
||||
unless missing_locales.empty?
|
||||
UI.user_error!(
|
||||
"App Store Connect localizations are missing for screenshot locales #{missing_locales.join(', ')}. " \
|
||||
"Upload metadata for these locales before uploading screenshots."
|
||||
)
|
||||
end
|
||||
|
||||
screenshots_by_target.keys.map(&:first).uniq.each do |locale|
|
||||
clear_app_store_screenshot_sets!(localization: localizations_by_locale.fetch(locale))
|
||||
end
|
||||
|
||||
screenshots_by_target.each do |(locale, display_type), target_screenshots|
|
||||
replace_app_store_screenshot_set!(
|
||||
localization: localizations_by_locale.fetch(locale),
|
||||
display_type: display_type,
|
||||
screenshots: target_screenshots
|
||||
)
|
||||
end
|
||||
|
||||
UI.success("Uploaded and verified #{screenshots.length} App Store screenshots for #{short_version}.")
|
||||
end
|
||||
|
||||
def read_ios_version_metadata
|
||||
script_path = File.join(repo_root, "scripts", "ios-version.ts")
|
||||
stdout, stderr, status = Open3.capture3(
|
||||
@@ -1128,58 +828,6 @@ def prepare_app_store_release!(version:, build_number:)
|
||||
release_xcconfig
|
||||
end
|
||||
|
||||
def mobile_release_ref_script
|
||||
File.join(repo_root, "scripts", "mobile-release-ref.ts")
|
||||
end
|
||||
|
||||
def release_git_sha
|
||||
stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
|
||||
UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
|
||||
stdout.strip
|
||||
end
|
||||
|
||||
def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
args = [
|
||||
"node",
|
||||
"--import",
|
||||
"tsx",
|
||||
mobile_release_ref_script,
|
||||
command,
|
||||
"--platform",
|
||||
platform,
|
||||
"--version",
|
||||
version,
|
||||
"--root",
|
||||
repo_root,
|
||||
]
|
||||
args.push("--build", build.to_s) if build
|
||||
args.push("--version-code", version_code.to_s) if version_code
|
||||
args.push("--sha", sha.to_s) if sha
|
||||
sh(shell_join(args))
|
||||
end
|
||||
|
||||
def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"preflight",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"record",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def validate_app_store_ipa!(ipa_path)
|
||||
script_path = File.join(repo_root, "scripts", "ios-validate-app-store-ipa.sh")
|
||||
sh(shell_join(["bash", script_path, "--ipa", ipa_path]))
|
||||
@@ -1355,28 +1003,21 @@ platform :ios do
|
||||
ENV.delete("XCODE_XCCONFIG_FILE")
|
||||
end
|
||||
|
||||
desc "Generate screenshots, update App Store metadata and review attachment, then upload an App Store build"
|
||||
desc "Generate screenshots, update App Store version metadata, then upload an App Store build"
|
||||
lane :release_upload do
|
||||
unless ENV["OPENCLAW_IOS_RELEASE_WRAPPER"] == "1"
|
||||
UI.user_error!("Use `pnpm ios:release:upload`; direct Fastlane TestFlight upload is disabled.")
|
||||
end
|
||||
|
||||
release_sha = release_git_sha
|
||||
release_signing_check!
|
||||
preserve_local_signing do
|
||||
screenshots
|
||||
end
|
||||
context = prepare_app_store_context(require_api_key: true)
|
||||
ensure_mobile_release_ref_available!(
|
||||
platform: "ios",
|
||||
version: context[:short_version],
|
||||
build: context[:build_number],
|
||||
sha: release_sha
|
||||
)
|
||||
ENV["DELIVER_SCREENSHOTS"] = "1"
|
||||
ENV["DELIVER_RELEASE_NOTES"] = "1"
|
||||
metadata
|
||||
|
||||
context = prepare_app_store_context(require_api_key: true)
|
||||
build = build_app_store_release(context)
|
||||
|
||||
upload_to_testflight(
|
||||
@@ -1385,12 +1026,6 @@ platform :ios do
|
||||
skip_waiting_for_build_processing: true,
|
||||
uses_non_exempt_encryption: false
|
||||
)
|
||||
record_mobile_release_ref!(
|
||||
platform: "ios",
|
||||
version: build[:short_version],
|
||||
build: build[:build_number],
|
||||
sha: release_sha
|
||||
)
|
||||
|
||||
UI.success("Uploaded iOS App Store build: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}")
|
||||
UI.important("App Review submission remains manual in App Store Connect.")
|
||||
@@ -1398,7 +1033,7 @@ platform :ios do
|
||||
ENV.delete("XCODE_XCCONFIG_FILE")
|
||||
end
|
||||
|
||||
desc "Upload App Store metadata, App Review PDF attachment, and optionally screenshots"
|
||||
desc "Upload App Store metadata (and optionally screenshots)"
|
||||
lane :metadata do
|
||||
install_ready_for_review_edit_state_lookup!
|
||||
sync_ios_versioning!
|
||||
@@ -1411,22 +1046,19 @@ platform :ios do
|
||||
app_id = nil unless env_present?(app_id)
|
||||
|
||||
if screenshot_upload_requested?
|
||||
screenshots_to_upload = app_store_screenshot_manifest
|
||||
if screenshots_to_upload.empty?
|
||||
paths = screenshot_paths
|
||||
if paths.empty?
|
||||
UI.user_error!("DELIVER_SCREENSHOTS=1 but no PNG screenshots were found under apps/ios/fastlane/screenshots.")
|
||||
end
|
||||
validate_required_screenshots!(screenshots_to_upload.map(&:path))
|
||||
validate_required_screenshots!(paths)
|
||||
end
|
||||
|
||||
assert_no_app_review_notes_field_metadata!(File.join(__dir__, "metadata"))
|
||||
metadata_path = public_metadata_path
|
||||
skip_metadata = ENV["DELIVER_METADATA"] != "1"
|
||||
if release_notes_upload_requested? && skip_metadata
|
||||
metadata_path = release_notes_metadata_path
|
||||
skip_metadata = false
|
||||
end
|
||||
assert_no_app_review_notes_field_metadata!(metadata_path) unless skip_metadata
|
||||
app_review_attachment_file = skip_metadata ? nil : generate_app_review_notes_pdf!
|
||||
|
||||
deliver_options = {
|
||||
api_key: api_key,
|
||||
@@ -1436,11 +1068,10 @@ platform :ios do
|
||||
primary_category: "PRODUCTIVITY",
|
||||
secondary_category: "UTILITIES",
|
||||
metadata_path: metadata_path,
|
||||
skip_screenshots: true,
|
||||
skip_screenshots: !screenshot_upload_requested?,
|
||||
skip_metadata: skip_metadata,
|
||||
skip_binary_upload: true,
|
||||
overwrite_screenshots: false,
|
||||
app_review_attachment_file: app_review_attachment_file,
|
||||
overwrite_screenshots: screenshot_upload_requested?,
|
||||
skip_app_version_update: false,
|
||||
submit_for_review: false,
|
||||
run_precheck_before_submit: false
|
||||
@@ -1453,14 +1084,6 @@ platform :ios do
|
||||
end
|
||||
|
||||
deliver(**deliver_options)
|
||||
if screenshot_upload_requested?
|
||||
upload_app_store_screenshots_deterministically!(
|
||||
app_identifier: app_identifier,
|
||||
app_id: app_id,
|
||||
short_version: version_metadata[:short_version],
|
||||
screenshots: screenshots_to_upload
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
desc "Generate deterministic iOS screenshots for App Store metadata"
|
||||
|
||||
@@ -169,5 +169,5 @@ Versioning rules:
|
||||
- Local App Store signing uses a temporary generated xcconfig with profile names from `apps/ios/Config/AppStoreSigning.json` and leaves local development signing overrides untouched
|
||||
- App Store release uses `OpenClawPushMode=appStore`, which derives the canonical production hosted relay, production APNs, production relay profile, and `appleStrict` proof. The release lane rejects custom production relay URL overrides.
|
||||
- The exported IPA is validated before upload by inspecting its push mode, signed entitlements, and embedded App Store profile.
|
||||
- `pnpm ios:release:upload` generates and uploads screenshots, release notes, and the App Review PDF attachment before archiving, then uploads the IPA without submitting it for App Review or uploading the App Store Connect `Notes` field
|
||||
- `pnpm ios:release:upload` generates and uploads screenshots and release notes before archiving, then uploads the IPA without submitting it for App Review
|
||||
- See `apps/ios/VERSIONING.md` for the detailed workflow
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This directory is used by `fastlane deliver` for App Store Connect text metadata.
|
||||
|
||||
## Upload public metadata and App Review attachment
|
||||
## Upload metadata only
|
||||
|
||||
```bash
|
||||
cd apps/ios
|
||||
@@ -10,9 +10,9 @@ APP_STORE_CONNECT_APP_ID=YOUR_APP_STORE_CONNECT_APP_ID \
|
||||
DELIVER_METADATA=1 fastlane ios metadata
|
||||
```
|
||||
|
||||
## Release notes and App Review attachment
|
||||
## Release notes only
|
||||
|
||||
`pnpm ios:release:upload` uses this mode before archiving so the editable App Store version has current release notes and the App Review PDF attachment without rewriting all metadata:
|
||||
`pnpm ios:release:upload` uses this mode before archiving so the editable App Store version has current release notes without rewriting all metadata:
|
||||
|
||||
```bash
|
||||
cd apps/ios
|
||||
@@ -46,12 +46,11 @@ Or set `APP_STORE_CONNECT_API_KEY_PATH`.
|
||||
|
||||
- Locale files live under `metadata/en-US/`.
|
||||
- `release_notes.txt` is generated from `apps/ios/CHANGELOG.md`; after changelog updates, run `pnpm ios:version:sync`.
|
||||
- `apps/ios/APP-REVIEW-NOTES.md` is rendered to `apps/ios/build/app-review/APP-REVIEW-NOTES.pdf` and uploaded as the App Review attachment when metadata is uploaded.
|
||||
- Release notes resolve from `## <pinned iOS version>` first, then fall back to `## Unreleased` while a TestFlight train is still in progress.
|
||||
- When starting a new production release train, pin the iOS version first with `pnpm ios:version:pin -- --from-gateway`.
|
||||
- The release upload flow uploads release notes, screenshots, and the App Review PDF attachment before the IPA, and never submits for App Review.
|
||||
- The release upload flow uploads release notes and screenshots before the IPA, and never submits for App Review.
|
||||
- `privacy_url.txt` is set to `https://openclaw.ai/privacy`.
|
||||
- If app lookup fails in `deliver`, set one of:
|
||||
- `APP_STORE_CONNECT_APP_IDENTIFIER` (bundle ID)
|
||||
- `APP_STORE_CONNECT_APP_ID` (numeric App Store Connect app ID, e.g. from `/apps/<id>/...` URL)
|
||||
- App Review submission is manual. Keep review contact, demo account, and the App Store Connect `Notes` field outside this repo and enter them directly in App Store Connect when submitting for review. Do not add `metadata/review_information/notes.txt`; the lane refuses to upload that field.
|
||||
- App Review submission is manual. Keep review contact, demo account, and reviewer notes outside this repo and enter them directly in App Store Connect when submitting for review.
|
||||
|
||||
@@ -5,7 +5,7 @@ Pair this iOS app with your OpenClaw Gateway to use your iPhone as a secure node
|
||||
What you can do:
|
||||
- Pair with your private OpenClaw Gateway by QR code or setup code
|
||||
- Chat with your assistant from iPhone
|
||||
- Use realtime Talk mode and background Talk
|
||||
- Use realtime Talk mode and push-to-talk
|
||||
- Review Gateway action approvals from your iPhone
|
||||
- Share text, links, and media directly from iOS into OpenClaw
|
||||
- Enable device capabilities such as camera, screen, location, photos, contacts, calendar, and reminders when you choose
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
Maintenance update for the current OpenClaw beta release.
|
||||
Maintenance update for the current OpenClaw release.
|
||||
|
||||
- Improved notification cleanup, Watch app compatibility, and native file input handling.
|
||||
- Added Apple Watch controls for common agent actions.
|
||||
- Improved Gateway setup, notification settings, and share-extension identity handling.
|
||||
- Updated the Watch app integration for current Xcode compatibility.
|
||||
|
||||
@@ -156,7 +156,7 @@ targets:
|
||||
NSAllowsLocalNetworking: true
|
||||
NSBonjourServices:
|
||||
- _openclaw-gw._tcp
|
||||
NSCameraUsageDescription: OpenClaw uses the camera when you scan a Gateway setup QR code or ask your paired Gateway or assistant to capture a photo or short video from this iPhone, for example to connect to your Gateway or show your assistant a document, device screen, or workspace.
|
||||
NSCameraUsageDescription: OpenClaw can capture photos or short video clips when requested via the gateway.
|
||||
NSCalendarsUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.
|
||||
NSCalendarsFullAccessUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.
|
||||
NSCalendarsWriteOnlyAccessUsageDescription: OpenClaw uses your calendars to add events when you enable calendar access.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2026.6.10"
|
||||
"version": "2026.6.9"
|
||||
}
|
||||
|
||||
@@ -108,6 +108,24 @@
|
||||
"revision" : "7c6ad0fc39d0763e0b699210e4124afd5041c5df",
|
||||
"version" : "1.6.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftui-math",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/gonzalezreal/swiftui-math",
|
||||
"state" : {
|
||||
"revision" : "0b5c2cfaaec8d6193db206f675048eeb5ce95f71",
|
||||
"version" : "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "textual",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/gonzalezreal/textual",
|
||||
"state" : {
|
||||
"revision" : "5b06b811c0f5313b6b84bbef98c635a630638c38",
|
||||
"version" : "0.3.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
|
||||
@@ -20,7 +20,6 @@ let package = Package(
|
||||
.package(url: "https://github.com/apple/swift-log.git", from: "1.10.1"),
|
||||
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.9.0"),
|
||||
.package(url: "https://github.com/steipete/Peekaboo.git", exact: "3.5.2"),
|
||||
.package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.3.1"),
|
||||
.package(path: "../shared/OpenClawKit"),
|
||||
.package(path: "../swabble"),
|
||||
],
|
||||
@@ -55,7 +54,6 @@ let package = Package(
|
||||
.product(name: "Sparkle", package: "Sparkle"),
|
||||
.product(name: "PeekabooBridge", package: "Peekaboo"),
|
||||
.product(name: "PeekabooAutomationKit", package: "Peekaboo"),
|
||||
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
|
||||
],
|
||||
exclude: [
|
||||
"Resources/Info.plist",
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.6.10</string>
|
||||
<string>2026.6.9</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2026061000</string>
|
||||
<string>2026060900</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -19,6 +19,7 @@ let package = Package(
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/steipete/ElevenLabsKit", exact: "0.1.1"),
|
||||
.package(url: "https://github.com/gonzalezreal/textual", exact: "0.3.1"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@@ -44,6 +45,10 @@ let package = Package(
|
||||
name: "OpenClawChatUI",
|
||||
dependencies: [
|
||||
"OpenClawKit",
|
||||
.product(
|
||||
name: "Textual",
|
||||
package: "textual",
|
||||
condition: .when(platforms: [.macOS, .iOS])),
|
||||
],
|
||||
path: "Sources/OpenClawChatUI",
|
||||
swiftSettings: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Textual
|
||||
|
||||
public enum ChatMarkdownVariant: String, CaseIterable, Sendable {
|
||||
case standard
|
||||
@@ -22,28 +22,46 @@ struct ChatMarkdownRenderer: View {
|
||||
var body: some View {
|
||||
let processed = ChatMarkdownPreprocessor.preprocess(markdown: self.text)
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text(self.markdownText(processed.cleaned))
|
||||
.font(self.font)
|
||||
.foregroundStyle(self.textColor)
|
||||
.tint(self.linkColor)
|
||||
.textSelection(.enabled)
|
||||
.lineSpacing(self.variant == .compact ? 2 : 4)
|
||||
StructuredText(markdown: processed.cleaned)
|
||||
.modifier(ChatMarkdownStyle(
|
||||
variant: self.variant,
|
||||
context: self.context,
|
||||
font: self.font,
|
||||
textColor: self.textColor))
|
||||
|
||||
if !processed.images.isEmpty {
|
||||
InlineImageList(images: processed.images)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var linkColor: Color {
|
||||
self.context == .user ? self.textColor : OpenClawChatTheme.accent
|
||||
private struct ChatMarkdownStyle: ViewModifier {
|
||||
let variant: ChatMarkdownVariant
|
||||
let context: ChatMarkdownRenderer.Context
|
||||
let font: Font
|
||||
let textColor: Color
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
Group {
|
||||
if self.variant == .compact {
|
||||
content.textual.structuredTextStyle(.default)
|
||||
} else {
|
||||
content.textual.structuredTextStyle(.gitHub)
|
||||
}
|
||||
}
|
||||
.font(self.font)
|
||||
.foregroundStyle(self.textColor)
|
||||
.textual.inlineStyle(self.inlineStyle)
|
||||
.textual.textSelection(.enabled)
|
||||
}
|
||||
|
||||
private func markdownText(_ markdown: String) -> AttributedString {
|
||||
let options = AttributedString.MarkdownParsingOptions(
|
||||
interpretedSyntax: .full,
|
||||
failurePolicy: .returnPartiallyParsedIfPossible)
|
||||
return (try? AttributedString(markdown: markdown, options: options)) ?? AttributedString(markdown)
|
||||
private var inlineStyle: InlineStyle {
|
||||
let linkColor: Color = self.context == .user ? self.textColor : OpenClawChatTheme.accent
|
||||
let codeScale: CGFloat = self.variant == .compact ? 0.85 : 0.9
|
||||
return InlineStyle()
|
||||
.code(.monospaced, .fontScale(codeScale))
|
||||
.link(.foregroundColor(linkColor))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
f5a5855ddd7aa8c23a732f257eceaa20fd163b1d5f342c909f4aef15aa8643cf config-baseline.json
|
||||
b8dffdb1a328aaf728a0707ab04d21c65f1a225a2360042e10832aa608699716 config-baseline.core.json
|
||||
671979e86e4c4f59415d0a20879e838f9bbd883b3d29eeb02cb5131db8d187fe config-baseline.channel.json
|
||||
94529978588d6e3776a86780b22cf9ff46a6f9957f2f178d3829403fad451ca7 config-baseline.plugin.json
|
||||
ee542300a1f9d5c23e772d47f2acfcc92ee0a4da210974306790bf2220b80277 config-baseline.json
|
||||
6349131baaa1828f2a071f42e4d7b17c8966c59b6588c8a4c1a32ea5ea4dcd5e config-baseline.core.json
|
||||
de674ef01dad2828bb711a4648dc5a00f696f71c3c59004131d9475769bc1ff8 config-baseline.channel.json
|
||||
ce2a731077f0f0135b7eaf01b00a60abfa0d2776aba4be237491d492af0c8a02 config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
760812c17f7e48d7ceafeebbbe348dad13916ccb9ecaf41b3abc9a09b1e690c1 plugin-sdk-api-baseline.json
|
||||
4d9b76016b2f845e101949a3d2ac92437f49783906d1c263d65f3534bb333de5 plugin-sdk-api-baseline.jsonl
|
||||
da3373338b7f9c5f5639ad8233a32897d2346a0babe69a77386a7bff154cdcb1 plugin-sdk-api-baseline.json
|
||||
17404d885e0d64ebc8e3c99443921058a8f1aebf76a5e612eb1f0cd7817d48f0 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -11,7 +11,7 @@ Generated locale trees and live translation memory now live in the publish repo:
|
||||
|
||||
- English docs are authored in `openclaw/openclaw`.
|
||||
- The source docs tree lives under `docs/`.
|
||||
- The source repo no longer keeps committed generated locale trees such as `docs/zh-CN/**`, `docs/zh-TW/**`, `docs/ja-JP/**`, `docs/es/**`, `docs/pt-BR/**`, `docs/ko/**`, `docs/de/**`, `docs/fr/**`, `docs/hi/**`, `docs/ar/**`, `docs/it/**`, `docs/vi/**`, `docs/nl/**`, `docs/fa/**`, `docs/ru/**`, `docs/tr/**`, `docs/uk/**`, `docs/id/**`, `docs/pl/**`, or `docs/th/**`.
|
||||
- The source repo no longer keeps committed generated locale trees such as `docs/zh-CN/**`, `docs/zh-TW/**`, `docs/ja-JP/**`, `docs/es/**`, `docs/pt-BR/**`, `docs/ko/**`, `docs/de/**`, `docs/fr/**`, `docs/ar/**`, `docs/it/**`, `docs/vi/**`, `docs/nl/**`, `docs/fa/**`, `docs/tr/**`, `docs/uk/**`, `docs/id/**`, `docs/pl/**`, or `docs/th/**`.
|
||||
|
||||
## End-to-end flow
|
||||
|
||||
@@ -32,10 +32,10 @@ Generated locale trees and live translation memory now live in the publish repo:
|
||||
|
||||
## Locale visibility
|
||||
|
||||
- Control UI supports `en`, `zh-CN`, `zh-TW`, `pt-BR`, `de`, `es`, `ja-JP`, `ko`, `fr`, `hi`, `ar`, `it`, `vi`, `nl`, `fa`, `ru`, `tr`, `uk`, `id`, `pl`, and `th`.
|
||||
- Control UI supports `en`, `zh-CN`, `zh-TW`, `pt-BR`, `de`, `es`, `ja-JP`, `ko`, `fr`, `ar`, `it`, `tr`, `uk`, `id`, `pl`, `th`, `vi`, `nl`, and `fa`.
|
||||
- Docs translation workflows generate the same non-English locale set in `openclaw/docs`.
|
||||
- The Mintlify docs language picker can expose only the locales accepted by Mintlify `navigation.languages`; Russian (`ru`) and Hindi (`hi`) are now included in the publish configuration.
|
||||
- Do not treat locale visibility in generated `docs/docs.json` as proof that translation artifacts exist. Verify each generated locale folder and its translation memory in `openclaw/docs`.
|
||||
- The Mintlify docs language picker can expose only the locales accepted by Mintlify `navigation.languages`; today that includes Vietnamese (`vi`) and Dutch (`nl`), but not Thai (`th`) or Persian (`fa`).
|
||||
- Do not treat missing `th` or `fa` entries in generated `docs/docs.json` as a pipeline failure. Verify their generated folders in `openclaw/docs` instead.
|
||||
|
||||
## Files in this folder
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
[
|
||||
{
|
||||
"source": "ACP",
|
||||
"target": "ACP"
|
||||
},
|
||||
{
|
||||
"source": "Active Memory",
|
||||
"target": "Active Memory"
|
||||
},
|
||||
{
|
||||
"source": "ClawHub",
|
||||
"target": "ClawHub"
|
||||
},
|
||||
{
|
||||
"source": "CLI",
|
||||
"target": "CLI"
|
||||
},
|
||||
{
|
||||
"source": "Compaction",
|
||||
"target": "Compaction"
|
||||
},
|
||||
{
|
||||
"source": "Cron",
|
||||
"target": "Cron"
|
||||
},
|
||||
{
|
||||
"source": "Dreaming",
|
||||
"target": "Dreaming"
|
||||
},
|
||||
{
|
||||
"source": "Gateway",
|
||||
"target": "Gateway"
|
||||
},
|
||||
{
|
||||
"source": "Heartbeat",
|
||||
"target": "Heartbeat"
|
||||
},
|
||||
{
|
||||
"source": "LINE",
|
||||
"target": "LINE"
|
||||
},
|
||||
{
|
||||
"source": "Mintlify",
|
||||
"target": "Mintlify"
|
||||
},
|
||||
{
|
||||
"source": "Node",
|
||||
"target": "Node"
|
||||
},
|
||||
{
|
||||
"source": "OpenClaw",
|
||||
"target": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"source": "Pi",
|
||||
"target": "Pi"
|
||||
},
|
||||
{
|
||||
"source": "Plugin",
|
||||
"target": "Plugin"
|
||||
},
|
||||
{
|
||||
"source": "Skills",
|
||||
"target": "Skills"
|
||||
},
|
||||
{
|
||||
"source": "Tailscale",
|
||||
"target": "Tailscale"
|
||||
},
|
||||
{
|
||||
"source": "TaskFlow",
|
||||
"target": "TaskFlow"
|
||||
},
|
||||
{
|
||||
"source": "TUI",
|
||||
"target": "TUI"
|
||||
},
|
||||
{
|
||||
"source": "Webhook",
|
||||
"target": "Webhook"
|
||||
}
|
||||
]
|
||||
@@ -1,82 +0,0 @@
|
||||
[
|
||||
{
|
||||
"source": "ACP",
|
||||
"target": "ACP"
|
||||
},
|
||||
{
|
||||
"source": "Active Memory",
|
||||
"target": "Active Memory"
|
||||
},
|
||||
{
|
||||
"source": "ClawHub",
|
||||
"target": "ClawHub"
|
||||
},
|
||||
{
|
||||
"source": "CLI",
|
||||
"target": "CLI"
|
||||
},
|
||||
{
|
||||
"source": "Compaction",
|
||||
"target": "Compaction"
|
||||
},
|
||||
{
|
||||
"source": "Cron",
|
||||
"target": "Cron"
|
||||
},
|
||||
{
|
||||
"source": "Dreaming",
|
||||
"target": "Dreaming"
|
||||
},
|
||||
{
|
||||
"source": "Gateway",
|
||||
"target": "Gateway"
|
||||
},
|
||||
{
|
||||
"source": "Heartbeat",
|
||||
"target": "Heartbeat"
|
||||
},
|
||||
{
|
||||
"source": "LINE",
|
||||
"target": "LINE"
|
||||
},
|
||||
{
|
||||
"source": "Mintlify",
|
||||
"target": "Mintlify"
|
||||
},
|
||||
{
|
||||
"source": "Node",
|
||||
"target": "Node"
|
||||
},
|
||||
{
|
||||
"source": "OpenClaw",
|
||||
"target": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"source": "Pi",
|
||||
"target": "Pi"
|
||||
},
|
||||
{
|
||||
"source": "Plugin",
|
||||
"target": "Plugin"
|
||||
},
|
||||
{
|
||||
"source": "Skills",
|
||||
"target": "Skills"
|
||||
},
|
||||
{
|
||||
"source": "Tailscale",
|
||||
"target": "Tailscale"
|
||||
},
|
||||
{
|
||||
"source": "TaskFlow",
|
||||
"target": "TaskFlow"
|
||||
},
|
||||
{
|
||||
"source": "TUI",
|
||||
"target": "TUI"
|
||||
},
|
||||
{
|
||||
"source": "Webhook",
|
||||
"target": "Webhook"
|
||||
}
|
||||
]
|
||||
@@ -24,14 +24,6 @@ This directory owns docs authoring, Mintlify link rules, and docs i18n policy.
|
||||
- `scripts/docs-sync-publish.mjs` excludes and prunes `docs/internal/**` from the public `openclaw/docs` publish repo if a page is force-added later.
|
||||
- Internal docs may mention repo paths, private app names, 1Password item names, and runbooks, but never include secret values.
|
||||
|
||||
## Maturity Scorecard Editing
|
||||
|
||||
`taxonomy.yaml` and `qa/maturity-scores.yaml` are the source inputs; generated maturity docs under `docs/maturity/` are projections and should not be hand-edited for score, LTS, taxonomy, QA profile, or evidence tables.
|
||||
`scripts/qa/render-maturity-docs.ts` owns generation; use `pnpm maturity:render` to refresh committed docs and `pnpm maturity:check` to verify them.
|
||||
`.github/workflows/maturity-scorecard.yml` renders artifact previews and can open generated-doc PRs; `.github/workflows/openclaw-release-checks.yml` dispatches it for release QA.
|
||||
Keep deterministic `qa-evidence.json.scorecard` data in GitHub Actions artifacts unless a maintainer explicitly asks for a sanitized committed projection.
|
||||
Human overrides must change source state in a PR and explain the reason plus public or redacted evidence.
|
||||
|
||||
## Docs i18n
|
||||
|
||||
- Foreign-language docs are not maintained in this repo. The generated publish output lives in the separate `openclaw/docs` repo (often cloned locally as `../openclaw-docs`).
|
||||
|
||||
@@ -579,7 +579,7 @@ When `imsg launch` is running and `openclaw channels status --probe` reports `pr
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Read receipts and typing">
|
||||
When the private API bridge is up, accepted inbound chats are marked read and direct chats show a typing bubble as soon as the turn is accepted, while the agent prepares context and generates. Disable read-marking with:
|
||||
When the private API bridge is up, accepted inbound chats are marked read before dispatch and a typing bubble is shown to the sender while the agent generates. Disable read-marking with:
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -155,7 +155,6 @@ Notes:
|
||||
|
||||
- `onchar` still responds to explicit @mentions.
|
||||
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
||||
- After the bot sends a visible reply in a channel thread, later messages in that same thread are answered without a new @mention or `onchar` prefix, so multi-turn thread conversations keep flowing. Participation is remembered for 7 days of thread inactivity (refreshed on each reply) and persists across gateway restarts. Threads the bot has only observed are unaffected; start a new top-level message to require an explicit mention again.
|
||||
|
||||
## Threading and sessions
|
||||
|
||||
|
||||
@@ -151,7 +151,6 @@ to a group, then mention it or configure the group to run without a mention.
|
||||
groups: {
|
||||
"*": {
|
||||
requireMention: true,
|
||||
commandLevel: "all",
|
||||
historyLimit: 50,
|
||||
tools: { deny: ["exec", "read", "write"] },
|
||||
},
|
||||
@@ -159,7 +158,6 @@ to a group, then mention it or configure the group to run without a mention.
|
||||
name: "Release room",
|
||||
requireMention: false,
|
||||
ignoreOtherMentions: true,
|
||||
commandLevel: "safety",
|
||||
historyLimit: 20,
|
||||
prompt: "Keep replies short and operational.",
|
||||
},
|
||||
@@ -174,9 +172,6 @@ to a group, then mention it or configure the group to run without a mention.
|
||||
settings include:
|
||||
|
||||
- `requireMention`: require an @mention before the bot replies. Default: `true`.
|
||||
- `commandLevel`: control which built-in slash commands can run in groups.
|
||||
Default: `all`, which preserves the pre-existing QQBot group behavior when the
|
||||
setting is omitted.
|
||||
- `ignoreOtherMentions`: drop messages that mention someone else but not the bot.
|
||||
- `historyLimit`: keep recent non-mention group messages as context for the next mentioned turn. Set `0` to disable.
|
||||
- `tools`: allow/deny tools for the whole group.
|
||||
@@ -184,17 +179,6 @@ settings include:
|
||||
- `name`: friendly label used in logs and group context.
|
||||
- `prompt`: per-group behavior prompt appended to the agent context.
|
||||
|
||||
`commandLevel` accepts:
|
||||
|
||||
- `all`: keep recognized built-in commands available as before. Some commands may
|
||||
stay hidden from menus, but authorized users can still run them in the group.
|
||||
- `safety`: allow common collaboration commands such as `/help`, `/btw`, and
|
||||
`/stop`; ask users to run sensitive commands such as `/config`, `/tools`, and
|
||||
`/bash` in private chat.
|
||||
- `strict`: only allow the group-session controls needed for strict group
|
||||
operation. `/stop` still stays urgent so an authorized sender can interrupt an
|
||||
active run.
|
||||
|
||||
Old QQBot `toolPolicy` entries are retired. Run `openclaw doctor --fix` to migrate them to `tools`.
|
||||
|
||||
Activation modes are `mention` and `always`. `requireMention: true` maps to
|
||||
|
||||
53
docs/ci.md
53
docs/ci.md
@@ -30,7 +30,7 @@ or an explicit manual dispatch.
|
||||
| `security-fast` | Private key detection, changed-workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs |
|
||||
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
|
||||
| `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, embedded built-artifact checks, and reusable artifacts | Node-relevant changes |
|
||||
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, QA Smoke CI, and CI-routing checks | Node-relevant changes |
|
||||
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, and CI-routing checks | Node-relevant changes |
|
||||
| `checks-fast-contracts-plugins-*` | Two sharded plugin contract checks | Node-relevant changes |
|
||||
| `checks-fast-contracts-channels-*` | Two sharded channel contract checks | Node-relevant changes |
|
||||
| `checks-node-core-*` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
|
||||
@@ -42,7 +42,6 @@ or an explicit manual dispatch.
|
||||
| `checks-windows` | Windows-specific process/path tests plus shared runtime import specifier regressions | Windows-relevant changes |
|
||||
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
|
||||
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
|
||||
| `ios-build` | Xcode project generation plus the iOS app simulator build | iOS app, shared app kit, or Swabble changes |
|
||||
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
|
||||
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
|
||||
| `openclaw-performance` | Daily/on-demand Kova runtime performance reports with mock-provider, deep-profile, and GPT 5.5 live lanes | Scheduled and manual dispatch |
|
||||
@@ -53,7 +52,7 @@ or an explicit manual dispatch.
|
||||
2. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
|
||||
3. `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
|
||||
4. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
|
||||
5. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-core-*`, `checks-windows`, `macos-node`, `macos-swift`, `ios-build`, and `android`.
|
||||
5. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-core-*`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
|
||||
|
||||
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Matrix jobs use `fail-fast: false`, and `build-artifacts` reports embedded channel, core-support-boundary, and gateway-watch failures directly instead of queuing tiny verifier jobs. The automatic CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs. Manual full-suite runs use `CI-manual-v1-*` and do not cancel in-progress runs.
|
||||
|
||||
@@ -81,7 +80,7 @@ When the check fails, update the PR body instead of pushing another code commit.
|
||||
|
||||
Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`. Manual dispatch skips changed-scope detection and makes the preflight manifest act as if every scoped area changed.
|
||||
|
||||
- **CI workflow edits** validate the Node CI graph plus workflow linting, but do not force Windows, iOS, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes.
|
||||
- **CI workflow edits** validate the Node CI graph plus workflow linting, but do not force Windows, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes.
|
||||
- **Workflow Sanity** runs `actionlint`, `zizmor` over all workflow YAML files, the composite-action interpolation guard, and the conflict-marker guard. The PR-scoped `security-fast` job also runs `zizmor` over changed workflow files so workflow security findings fail early in the main CI graph.
|
||||
- **Docs on `main` pushes** are checked by the standalone `Docs` workflow with the same ClawHub docs mirror used by CI, so mixed code+docs pushes do not also queue the CI `check-docs` shard. Pull requests and manual CI still run `check-docs` from CI when docs changed.
|
||||
- **TUI PTY** runs in the `checks-node-core-runtime-tui-pty` Linux Node shard for TUI changes. The shard runs `test/vitest/vitest.tui-pty.config.ts` with `OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1`, so it covers both the deterministic `TuiBackend` fixture lane and the slower `tui --local` smoke that mocks only the external model endpoint.
|
||||
@@ -90,9 +89,9 @@ Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests
|
||||
|
||||
The slowest Node test families are split or balanced so each job stays small without over-reserving runners: plugin contracts and channel contracts each run as two weighted Blacksmith-backed shards with the standard GitHub runner fallback, core unit fast/support lanes run separately, core runtime infra is split between state, process/config, shared, and three cron domain shards, auto-reply runs as balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/server configs are split across chat/auth/model/http-plugin/runtime/startup lanes instead of waiting on built artifacts. Normal CI then packs only isolated infra include-pattern shards into deterministic bundles of at most 64 test files, reducing the Node matrix without merging non-isolated command/cron, stateful agents-core, or gateway/server suites; heavy fixed suites stay on 8 vCPU while the bundled and lower-weight lanes use 4 vCPU. Pull requests on the canonical repository use an additional compact admission plan: the same per-config groups run in isolated subprocesses inside the current 34-job Linux Node plan, so a single PR does not register the full 70-plus-job Node matrix. `main` pushes, manual dispatches, and release gates retain the full matrix. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional-*` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard list is striped into one prompt-heavy shard and one combined shard for the remaining guard stripes, each running selected independent guards concurrently and printing per-check timings. The expensive Codex happy-path prompt snapshot drift check runs as its own additional job for manual CI and for prompt-affecting changes only, so normal unrelated Node changes do not wait behind cold prompt snapshot generation and the boundary shards stay balanced while prompt drift is still pinned to the PR that caused it; the same flag skips prompt snapshot Vitest generation inside the built-artifact core support-boundary shard. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.
|
||||
|
||||
Once admitted, canonical Linux CI permits up to 24 concurrent Node test jobs and
|
||||
12 for the smaller fast/check lanes; Windows and Android stay at two because
|
||||
those runner pools are narrower.
|
||||
Once admitted, canonical Linux CI permits up to 12 concurrent Node jobs and 8 for
|
||||
the smaller fast/check lanes; Windows and Android stay at two because those
|
||||
runner pools are narrower.
|
||||
|
||||
The compact PR plan emits 18 Node jobs for the current suite: whole-config
|
||||
groups are batched in isolated subprocesses with a 120-minute batch timeout,
|
||||
@@ -121,7 +120,7 @@ Treat GitHub titles, comments, bodies, review text, branch names, and commit mes
|
||||
|
||||
## Manual dispatches
|
||||
|
||||
Manual CI dispatches run the same job graph as normal CI but force every non-Android scoped lane on: Linux Node shards, bundled-plugin shards, plugin and channel contract shards, Node 22 compatibility, `check-*`, `check-additional-*`, built-artifact smoke checks, docs checks, Python skills, Windows, macOS, iOS build, and Control UI i18n. Standalone manual CI dispatches run Android only with `include_android=true`; the full release umbrella enables Android by passing `include_android=true`. Plugin prerelease static checks, the release-only `agentic-plugins` shard, the full extension batch sweep, and plugin prerelease Docker lanes are excluded from CI. The Docker prerelease suite runs only when `Full Release Validation` dispatches the separate `Plugin Prerelease` workflow with the release-validation gate enabled.
|
||||
Manual CI dispatches run the same job graph as normal CI but force every non-Android scoped lane on: Linux Node shards, bundled-plugin shards, plugin and channel contract shards, Node 22 compatibility, `check-*`, `check-additional-*`, built-artifact smoke checks, docs checks, Python skills, Windows, macOS, and Control UI i18n. Standalone manual CI dispatches run Android only with `include_android=true`; the full release umbrella enables Android by passing `include_android=true`. Plugin prerelease static checks, the release-only `agentic-plugins` shard, the full extension batch sweep, and plugin prerelease Docker lanes are excluded from CI. The Docker prerelease suite runs only when `Full Release Validation` dispatches the separate `Plugin Prerelease` workflow with the release-validation gate enabled.
|
||||
|
||||
Manual runs use a unique concurrency group so a release-candidate full suite is not cancelled by another push or PR run on the same ref. The optional `target_ref` input lets a trusted caller run that graph against a branch, tag, or full commit SHA while using the workflow file from the selected dispatch ref.
|
||||
|
||||
@@ -133,30 +132,15 @@ gh workflow run full-release-validation.yml --ref main -f ref=<branch-or-sha>
|
||||
|
||||
## Runners
|
||||
|
||||
| Runner | Jobs |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `ubuntu-24.04` | Manual CI dispatch and non-canonical repository fallbacks, CodeQL JavaScript/actions quality scans, workflow-sanity, labeler, auto-response, docs workflows outside CI, and install-smoke preflight so the Blacksmith matrix can queue earlier |
|
||||
| `blacksmith-4vcpu-ubuntu-2404` | `preflight`, `security-fast`, lower-weight extension shards, `checks-fast-core`, plugin/channel contract shards, most bundled/lower-weight Linux Node shards, `check-guards`, `check-prod-types`, `check-test-types`, selected `check-additional-*` shards, and `check-dependencies` |
|
||||
| `blacksmith-8vcpu-ubuntu-2404` | Retained heavy Linux Node suites, boundary/extension-heavy `check-additional-*` shards, and `android` |
|
||||
| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) |
|
||||
| `blacksmith-8vcpu-windows-2025` | `checks-windows` |
|
||||
| `blacksmith-6vcpu-macos-15` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-15` |
|
||||
| `blacksmith-12vcpu-macos-26` | `macos-swift` and `ios-build` on `openclaw/openclaw`; forks fall back to `macos-26` |
|
||||
|
||||
## Runner registration budget
|
||||
|
||||
OpenClaw's current GitHub runner-registration bucket allows 3,000 self-hosted
|
||||
runner registrations per 5 minutes. The limit is shared by all Blacksmith runner
|
||||
registrations in the `openclaw` organization, so adding another Blacksmith
|
||||
installation does not add a new bucket.
|
||||
|
||||
Treat Blacksmith labels as the scarce resource for burst control. Jobs that
|
||||
only route, notify, summarize, select shards, or run short CodeQL scans should
|
||||
stay on GitHub-hosted runners unless they have measured Blacksmith-specific
|
||||
needs. Any new Blacksmith matrix, larger `max-parallel`, or high-frequency
|
||||
workflow must show its worst-case registration count and keep the org-level
|
||||
target below 2,000 registrations per 5 minutes, leaving headroom for concurrent
|
||||
repositories and retried jobs.
|
||||
| Runner | Jobs |
|
||||
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ubuntu-24.04` | Manual CI dispatch and non-canonical repository fallbacks, workflow-sanity, labeler, auto-response, docs workflows outside CI, and install-smoke preflight so the Blacksmith matrix can queue earlier |
|
||||
| `blacksmith-4vcpu-ubuntu-2404` | `CodeQL Critical Quality`, `preflight`, `security-fast`, lower-weight extension shards, `checks-fast-core`, plugin/channel contract shards, most bundled/lower-weight Linux Node shards, `check-guards`, `check-prod-types`, `check-test-types`, selected `check-additional-*` shards, and `check-dependencies` |
|
||||
| `blacksmith-8vcpu-ubuntu-2404` | Retained heavy Linux Node suites, boundary/extension-heavy `check-additional-*` shards, and `android` |
|
||||
| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) |
|
||||
| `blacksmith-8vcpu-windows-2025` | `checks-windows` |
|
||||
| `blacksmith-6vcpu-macos-15` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-15` |
|
||||
| `blacksmith-12vcpu-macos-26` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-26` |
|
||||
|
||||
Canonical-repo CI keeps Blacksmith as the default runner path for normal push and pull-request runs. `workflow_dispatch` and non-canonical repository runs use GitHub-hosted runners, but normal canonical runs do not currently probe Blacksmith queue health or automatically fall back to GitHub-hosted labels when Blacksmith is unavailable.
|
||||
|
||||
@@ -178,7 +162,6 @@ pnpm test:channels
|
||||
pnpm test:contracts:channels
|
||||
pnpm check:docs # docs format + lint + broken links
|
||||
pnpm build # build dist when CI artifact/smoke checks matter
|
||||
pnpm ios:build # generate and build the iOS app project
|
||||
pnpm ci:timings # summarize the latest origin/main push CI run
|
||||
pnpm ci:timings:recent # compare recent successful main CI runs
|
||||
node scripts/ci-run-timings.mjs <run-id> # summarize wall time, queue time, and slowest jobs
|
||||
@@ -215,7 +198,7 @@ Every lane uploads GitHub artifacts. When `CLAWGRIT_REPORTS_TOKEN` is configured
|
||||
|
||||
## Full Release Validation
|
||||
|
||||
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, cross-OS package checks, maturity scorecard rendering from QA profile evidence, QA Lab parity, Matrix, and Telegram lanes. Stable and full profiles always include exhaustive live/E2E and Docker release-path soak coverage; the beta profile can opt in with `run_release_soak=true`. The canonical package Telegram E2E runs inside Package Acceptance, so a full candidate does not start a duplicate live poller. After publishing, pass `release_package_spec` to reuse the shipped npm package across release checks, Package Acceptance, Docker, cross-OS, and Telegram without rebuilding. Use `npm_telegram_package_spec` only for a focused published-package Telegram rerun. The Codex plugin live package lane uses the same selected state by default: published `release_package_spec=openclaw@<tag>` derives `codex_plugin_spec=npm:@openclaw/codex@<tag>`, while SHA/artifact runs pack `extensions/codex` from the selected ref. Set `codex_plugin_spec` explicitly for custom plugin sources such as `npm:`, `npm-pack:`, or `git:` specs.
|
||||
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, cross-OS package checks, QA Lab parity, Matrix, and Telegram lanes. Stable and full profiles always include exhaustive live/E2E and Docker release-path soak coverage; the beta profile can opt in with `run_release_soak=true`. The canonical package Telegram E2E runs inside Package Acceptance, so a full candidate does not start a duplicate live poller. After publishing, pass `release_package_spec` to reuse the shipped npm package across release checks, Package Acceptance, Docker, cross-OS, and Telegram without rebuilding. Use `npm_telegram_package_spec` only for a focused published-package Telegram rerun. The Codex plugin live package lane uses the same selected state by default: published `release_package_spec=openclaw@<tag>` derives `codex_plugin_spec=npm:@openclaw/codex@<tag>`, while SHA/artifact runs pack `extensions/codex` from the selected ref. Set `codex_plugin_spec` explicitly for custom plugin sources such as `npm:`, `npm-pack:`, or `git:` specs.
|
||||
|
||||
See [Full release validation](/reference/full-release-validation) for the
|
||||
stage matrix, exact workflow job names, profile differences, artifacts, and
|
||||
@@ -503,7 +486,7 @@ The pull request guard stays light: it only starts for changes under `.github/ac
|
||||
|
||||
### Critical Quality categories
|
||||
|
||||
`CodeQL Critical Quality` is the matching non-security shard. It runs only error-severity, non-security JavaScript/TypeScript quality queries over narrow high-value surfaces on GitHub-hosted Linux runners so quality scans do not spend Blacksmith runner-registration budget. Its pull request guard is intentionally smaller than the scheduled profile: non-draft PRs only run the matching `agent-runtime-boundary`, `config-boundary`, `core-auth-secrets`, `channel-runtime-boundary`, `gateway-runtime-boundary`, `memory-runtime-boundary`, `mcp-process-runtime-boundary`, `provider-runtime-boundary`, `session-diagnostics-boundary`, `plugin-boundary`, `plugin-sdk-package-contract`, and `plugin-sdk-reply-runtime` shards for agent command/model/tool execution and reply dispatch code, config schema/migration/IO code, auth/secrets/sandbox/security code, core channel and bundled channel plugin runtime, gateway protocol/server-method, memory runtime/SDK glue, MCP/process/outbound delivery, provider runtime/model catalog, session diagnostics/delivery queues, plugin loader, Plugin SDK/package-contract, or Plugin SDK reply runtime changes. CodeQL config and quality workflow changes run all twelve PR quality shards.
|
||||
`CodeQL Critical Quality` is the matching non-security shard. It runs only error-severity, non-security JavaScript/TypeScript quality queries over narrow high-value surfaces on the smaller Blacksmith Linux runner. Its pull request guard is intentionally smaller than the scheduled profile: non-draft PRs only run the matching `agent-runtime-boundary`, `config-boundary`, `core-auth-secrets`, `channel-runtime-boundary`, `gateway-runtime-boundary`, `memory-runtime-boundary`, `mcp-process-runtime-boundary`, `provider-runtime-boundary`, `session-diagnostics-boundary`, `plugin-boundary`, `plugin-sdk-package-contract`, and `plugin-sdk-reply-runtime` shards for agent command/model/tool execution and reply dispatch code, config schema/migration/IO code, auth/secrets/sandbox/security code, core channel and bundled channel plugin runtime, gateway protocol/server-method, memory runtime/SDK glue, MCP/process/outbound delivery, provider runtime/model catalog, session diagnostics/delivery queues, plugin loader, Plugin SDK/package-contract, or Plugin SDK reply runtime changes. CodeQL config and quality workflow changes run all twelve PR quality shards.
|
||||
|
||||
Manual dispatch accepts:
|
||||
|
||||
|
||||
@@ -24,31 +24,17 @@ OpenClaw agent or Gateway.
|
||||
```bash
|
||||
openclaw skills search "calendar"
|
||||
openclaw skills install @owner/<slug>
|
||||
openclaw skills install @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills update @owner/<slug>
|
||||
openclaw skills update @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills verify @owner/<slug>
|
||||
openclaw skills verify <slug>
|
||||
|
||||
openclaw plugins search "calendar"
|
||||
openclaw plugins install clawhub:<package>
|
||||
openclaw plugins install clawhub:<package> --acknowledge-clawhub-risk
|
||||
openclaw plugins update <id-or-npm-spec>
|
||||
```
|
||||
|
||||
Skill installs target the active workspace `skills/` directory by default. Add
|
||||
`--global` to install into the shared managed skills directory.
|
||||
|
||||
OpenClaw checks the selected community ClawHub skill or plugin trust state
|
||||
before downloading it. Versioned community skill and plugin releases use
|
||||
exact-release trust metadata; resolver-backed GitHub skills rely on ClawHub's
|
||||
install resolver to enforce scan and force-install policy before it returns a
|
||||
pinned commit. Malicious or blocked community releases are refused. Risky
|
||||
community releases require review and `--acknowledge-clawhub-risk` when a
|
||||
non-interactive command should continue after that review.
|
||||
|
||||
Official ClawHub publishers/packages and bundled OpenClaw sources bypass this
|
||||
release-trust prompt and security-verdict fetch during install and update.
|
||||
|
||||
Plugin installs use the `clawhub:` prefix when you want ClawHub resolution
|
||||
instead of npm or another install source.
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ openclaw doctor
|
||||
openclaw doctor --lint
|
||||
openclaw doctor --lint --json
|
||||
openclaw doctor --lint --severity-min warning
|
||||
openclaw doctor --lint --all
|
||||
openclaw doctor --lint --allow-exec
|
||||
openclaw doctor --deep
|
||||
openclaw doctor --fix
|
||||
@@ -74,7 +73,6 @@ The targeted Discord capabilities probe reports the bot's effective channel perm
|
||||
- `--post-upgrade`: run post-upgrade plugin compatibility probes; emits findings to stdout; exits with code 1 if any error-level findings are present
|
||||
- `--json`: with `--lint`, emit JSON findings instead of human output; with `--post-upgrade`, emit a machine-readable JSON envelope (`{ probesRun, findings }`)
|
||||
- `--severity-min <level>`: with `--lint`, drop findings below `info`, `warning`, or `error`
|
||||
- `--all`: with `--lint`, run all registered checks, including opt-in checks excluded from the default automation set
|
||||
- `--skip <id>`: with `--lint`, skip a check id; repeat to skip more than one
|
||||
- `--only <id>`: with `--lint`, run only a check id; repeat to run a small selected set
|
||||
|
||||
@@ -84,14 +82,13 @@ The targeted Discord capabilities probe reports the bot's effective channel perm
|
||||
It uses the structured health-check path, does not prompt, and does not repair
|
||||
or rewrite config/state. Use it in CI, preflight scripts, and review workflows
|
||||
when you want machine-readable findings instead of guided repair prompts.
|
||||
Lint-output options such as `--json`, `--severity-min`, `--all`, `--only`, and `--skip`
|
||||
Lint-output options such as `--json`, `--severity-min`, `--only`, and `--skip`
|
||||
are only accepted with `--lint`.
|
||||
|
||||
```bash
|
||||
openclaw doctor --lint
|
||||
openclaw doctor --lint --severity-min warning
|
||||
openclaw doctor --lint --json
|
||||
openclaw doctor --lint --all
|
||||
openclaw doctor --lint --allow-exec
|
||||
openclaw doctor --lint --only core/doctor/gateway-config --json
|
||||
```
|
||||
@@ -133,13 +130,6 @@ Exit behavior:
|
||||
example, `openclaw doctor --lint --severity-min error` can print no findings and
|
||||
exit `0` even when lower-severity `info` or `warning` findings exist.
|
||||
|
||||
`--all` controls which checks are selected before severity filtering. The
|
||||
default lint run is the stable automation gate and excludes checks that are
|
||||
intentionally opt-in because they are deep, historical, or more likely to
|
||||
surface repairable legacy residue. Use `--all` when you want the complete lint
|
||||
inventory without listing each check id. `--only <id>` remains the most precise
|
||||
selector and can run any registered check by id.
|
||||
|
||||
## Structured Health Checks
|
||||
|
||||
Modern doctor checks use a small structured contract:
|
||||
@@ -196,7 +186,6 @@ Use `--only` and `--skip` when a workflow wants a focused gate:
|
||||
```bash
|
||||
openclaw doctor --lint --only core/doctor/gateway-config --json
|
||||
openclaw doctor --lint --skip core/doctor/skills-readiness
|
||||
openclaw doctor --lint --all --skip core/doctor/session-locks
|
||||
```
|
||||
|
||||
`--only` and `--skip` accept full check ids and may be repeated. If an `--only`
|
||||
|
||||
@@ -54,9 +54,8 @@ openclaw plugins update <id-or-npm-spec>
|
||||
openclaw plugins update --all
|
||||
openclaw plugins marketplace list <marketplace>
|
||||
openclaw plugins marketplace list <marketplace> --json
|
||||
openclaw plugins init my-tool --name "My Tool"
|
||||
openclaw plugins init my-provider --name "My Provider" --type provider
|
||||
openclaw plugins init my-provider --name "My Provider" --type provider --directory ./my-provider
|
||||
openclaw plugins init <id>
|
||||
openclaw plugins init <id> --directory ./my-plugin --name "My Plugin"
|
||||
openclaw plugins build --entry ./dist/index.js
|
||||
openclaw plugins build --entry ./dist/index.js --check
|
||||
openclaw plugins validate --entry ./dist/index.js
|
||||
@@ -87,15 +86,12 @@ npm run plugin:build
|
||||
npm run plugin:validate
|
||||
```
|
||||
|
||||
`plugins init` creates a minimal TypeScript tool plugin by default. The first
|
||||
argument is the plugin id; pass `--name` for the display name. OpenClaw uses the
|
||||
id for the default output directory and package naming. Tool scaffolds use
|
||||
`defineToolPlugin`.
|
||||
`plugins build` imports the built entry, reads its static tool metadata, writes
|
||||
`openclaw.plugin.json`, and keeps `package.json` `openclaw.extensions` aligned.
|
||||
`plugins validate` checks that the generated manifest, package metadata, and
|
||||
current entry export still agree. See [Tool Plugins](/plugins/tool-plugins) for
|
||||
the full tool-authoring workflow.
|
||||
`plugins init` creates a minimal TypeScript tool plugin that uses
|
||||
`defineToolPlugin`. `plugins build` imports that entry, reads its static tool
|
||||
metadata, writes `openclaw.plugin.json`, and keeps `package.json`
|
||||
`openclaw.extensions` aligned. `plugins validate` checks that the generated
|
||||
manifest, package metadata, and current entry export still agree. See
|
||||
[Tool Plugins](/plugins/tool-plugins) for the full authoring workflow.
|
||||
|
||||
The scaffold writes TypeScript source but generates metadata from the built
|
||||
`./dist/index.js` entry so the workflow also works with the published CLI. Use
|
||||
@@ -103,29 +99,6 @@ The scaffold writes TypeScript source but generates metadata from the built
|
||||
`plugins build --check` in CI to fail when generated metadata is stale without
|
||||
rewriting files.
|
||||
|
||||
### Provider Scaffold
|
||||
|
||||
```bash
|
||||
openclaw plugins init acme-models --name "Acme Models" --type provider
|
||||
cd acme-models
|
||||
npm install
|
||||
npm run build
|
||||
npm test
|
||||
npm run validate
|
||||
```
|
||||
|
||||
Provider scaffolds create a generic text/model provider plugin with OpenAI-compatible
|
||||
API-key plumbing, a built-in `npm run validate` script for `clawhub package
|
||||
validate`, ClawHub package metadata, and a manually dispatched GitHub workflow
|
||||
for future trusted publishing through GitHub Actions OIDC. Provider scaffolds do
|
||||
not generate skills and do not use `openclaw plugins build` or
|
||||
`openclaw plugins validate`; those commands are for the tool scaffold's
|
||||
generated metadata path.
|
||||
|
||||
Before publishing, replace the placeholder API base URL, model catalog, docs
|
||||
route, credential text, and README copy with real provider details. Use the
|
||||
generated README for first-time ClawHub publishing and trusted publisher setup.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
@@ -138,7 +111,6 @@ openclaw plugins install git:github.com/<owner>/<repo> # git repo
|
||||
openclaw plugins install git:github.com/<owner>/<repo>@<ref>
|
||||
openclaw plugins install <package> --force # overwrite existing install
|
||||
openclaw plugins install <package> --pin # pin version
|
||||
openclaw plugins install clawhub:<package> --acknowledge-clawhub-risk
|
||||
openclaw plugins install <package> --dangerously-force-unsafe-install
|
||||
openclaw plugins install <path> # local path
|
||||
openclaw plugins install <plugin>@<marketplace> # marketplace
|
||||
@@ -191,12 +163,6 @@ is available, then fall back to `latest`.
|
||||
|
||||
If a plugin you published on ClawHub is hidden or blocked by a registry scan, use the publisher steps in [ClawHub publishing](/clawhub/publishing). `--dangerously-force-unsafe-install` does not ask ClawHub to rescan the plugin or make a blocked release public.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--acknowledge-clawhub-risk">
|
||||
Community ClawHub installs check the selected release trust record before downloading the package. If ClawHub disables download for the release, reports malicious scan findings, or puts the release in a blocking moderation state such as quarantine, OpenClaw refuses the release. For non-blocking risky scan statuses, risky moderation states, or registry reasons, OpenClaw shows the trust details and asks for confirmation before continuing.
|
||||
|
||||
Use `--acknowledge-clawhub-risk` only after reviewing the ClawHub warning and deciding to continue without an interactive prompt. Pending or stale clean trust records warn but do not require acknowledgement. Official ClawHub packages and bundled OpenClaw plugin sources bypass this release-trust prompt.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Hook packs and npm specs">
|
||||
`plugins install` is also the install surface for hook packs that expose `openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook visibility and per-hook enablement, not package installation.
|
||||
@@ -424,7 +390,6 @@ openclaw plugins update <id-or-npm-spec>
|
||||
openclaw plugins update --all
|
||||
openclaw plugins update <id-or-npm-spec> --dry-run
|
||||
openclaw plugins update @openclaw/voice-call
|
||||
openclaw plugins update openclaw-codex-app-server --acknowledge-clawhub-risk
|
||||
openclaw plugins update openclaw-codex-app-server --dangerously-force-unsafe-install
|
||||
```
|
||||
|
||||
@@ -434,17 +399,13 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
|
||||
<Accordion title="Resolving plugin id vs npm spec">
|
||||
When you pass a plugin id, OpenClaw reuses the recorded install spec for that plugin. That means previously stored dist-tags such as `@beta` and exact pinned versions continue to be used on later `update <id>` runs.
|
||||
|
||||
That targeted-update rule is different from the bulk `openclaw plugins update --all` maintenance path. Bulk updates still respect ordinary tracked install specs, but trusted official OpenClaw plugin records can sync to the current official catalog target instead of staying on a stale exact official package. Use targeted `update <id>` when you intentionally want to keep an exact or tagged official spec untouched.
|
||||
|
||||
For npm installs, you can also pass an explicit npm package spec with a dist-tag or exact version. OpenClaw resolves that package name back to the tracked plugin record, updates that installed plugin, and records the new npm spec for future id-based updates.
|
||||
|
||||
Passing the npm package name without a version or tag also resolves back to the tracked plugin record. Use this when a plugin was pinned to an exact version and you want to move it back to the registry's default release line.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Beta channel updates">
|
||||
Targeted `openclaw plugins update <id-or-npm-spec>` reuses the tracked plugin spec unless you pass a new spec. Bulk `openclaw plugins update --all` uses the configured `update.channel` when it syncs trusted official plugin records to the official catalog target, so beta-channel installs can stay on the beta release line instead of being silently normalized to stable/latest.
|
||||
|
||||
`openclaw update` also knows the active OpenClaw update channel: on the beta channel, default-line npm and ClawHub plugin records try `@beta` first. They fall back to the recorded default/latest spec if no plugin beta release exists; npm plugins also fall back when the beta package exists but fails install validation. That fallback is reported as a warning and does not fail the core update. Exact versions and explicit tags stay pinned to that selector for targeted updates.
|
||||
`openclaw plugins update` reuses the tracked plugin spec unless you pass a new spec. `openclaw update` additionally knows the active OpenClaw update channel: on the beta channel, default-line npm and ClawHub plugin records try `@beta` first. They fall back to the recorded default/latest spec if no plugin beta release exists; npm plugins also fall back when the beta package exists but fails install validation. That fallback is reported as a warning and does not fail the core update. Exact versions and explicit tags stay pinned to that selector.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Version checks and integrity drift">
|
||||
@@ -456,9 +417,6 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
|
||||
<Accordion title="--dangerously-force-unsafe-install on update">
|
||||
`--dangerously-force-unsafe-install` is also accepted on `plugins update` for compatibility, but it is deprecated and no longer changes plugin update behavior. Operator `security.installPolicy` can still block updates; plugin `before_install` hooks only apply in processes where plugin hooks are loaded.
|
||||
</Accordion>
|
||||
<Accordion title="--acknowledge-clawhub-risk on update">
|
||||
Community ClawHub-backed plugin updates run the same exact-release trust check as installs before downloading the replacement package. Use `--acknowledge-clawhub-risk` for reviewed automation that should continue when the selected ClawHub release has a risky trust warning. Official ClawHub packages and bundled OpenClaw plugin sources bypass this release-trust prompt.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Inspect
|
||||
|
||||
@@ -120,7 +120,6 @@ openclaw sessions cleanup --json
|
||||
|
||||
- Scope note: `openclaw sessions cleanup` maintains session stores, transcripts, and trajectory sidecars. It does not prune cron run history, which is managed by `cron.runLog.keepLines` in [Cron configuration](/automation/cron-jobs#configuration) and explained in [Cron maintenance](/automation/cron-jobs#maintenance).
|
||||
- Cleanup also prunes unreferenced primary transcripts, compaction checkpoints, and trajectory sidecars older than `session.maintenance.pruneAfter`; files still referenced by `sessions.json` are preserved.
|
||||
- Cleanup reports short-lived gateway model-run probe cleanup separately as `modelRunPruned`. This only matches strict explicit keys shaped like `agent:*:explicit:model-run-<uuid>`. The fixed retention is `24h`, but it is pressure-gated: it only removes stale probe rows when session-entry maintenance/cap pressure is reached. When it runs, model-run cleanup happens before global stale cleanup and capping.
|
||||
|
||||
- `--dry-run`: preview how many entries would be pruned/capped without writing.
|
||||
- In text mode, dry-run prints a per-session action table (`Action`, `Key`, `Age`, `Model`, `Flags`) plus a summary grouped by session label so you can see what would be kept vs removed.
|
||||
|
||||
@@ -31,20 +31,18 @@ openclaw skills install git:owner/repo
|
||||
openclaw skills install git:owner/repo@main
|
||||
openclaw skills install ./path/to/skill --as custom-name
|
||||
openclaw skills install @owner/<slug> --force
|
||||
openclaw skills install @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills install @owner/<slug> --agent <id>
|
||||
openclaw skills install @owner/<slug> --global
|
||||
openclaw skills update @owner/<slug>
|
||||
openclaw skills update @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills update @owner/<slug> --global
|
||||
openclaw skills update --all
|
||||
openclaw skills update --all --agent <id>
|
||||
openclaw skills update --all --global
|
||||
openclaw skills verify @owner/<slug>
|
||||
openclaw skills verify @owner/<slug> --version <version>
|
||||
openclaw skills verify @owner/<slug> --tag <tag>
|
||||
openclaw skills verify @owner/<slug> --card
|
||||
openclaw skills verify @owner/<slug> --global
|
||||
openclaw skills verify <slug>
|
||||
openclaw skills verify <slug> --version <version>
|
||||
openclaw skills verify <slug> --tag <tag>
|
||||
openclaw skills verify <slug> --card
|
||||
openclaw skills verify <slug> --global
|
||||
openclaw skills list
|
||||
openclaw skills list --eligible
|
||||
openclaw skills list --json
|
||||
@@ -99,14 +97,6 @@ Notes:
|
||||
- `install --version <version>` applies only to ClawHub skill refs.
|
||||
- `install --force` overwrites an existing workspace skill folder for the same
|
||||
slug.
|
||||
- Community ClawHub skill installs and updates check trust before downloading.
|
||||
Versioned community archive releases use exact-release trust metadata.
|
||||
Resolver-backed GitHub skills rely on ClawHub's install resolver to enforce
|
||||
scan and force-install policy before it returns a pinned commit. Malicious or
|
||||
blocked community releases are refused. Risky community releases require
|
||||
review and `--acknowledge-clawhub-risk` when a non-interactive command should
|
||||
continue after that review. Official ClawHub skill publishers and bundled
|
||||
OpenClaw skill sources bypass this release-trust prompt.
|
||||
- `--global` targets the shared managed skills directory and cannot be combined
|
||||
with `--agent <id>`.
|
||||
- `--agent <id>` targets one configured agent workspace and overrides current
|
||||
@@ -115,11 +105,8 @@ Notes:
|
||||
target the shared managed skills directory instead of the workspace.
|
||||
- `update --all` updates tracked ClawHub installs in the selected workspace, or
|
||||
in the shared managed skills directory when combined with `--global`.
|
||||
- `verify @owner/<slug>` prints ClawHub's `clawhub.skill.verify.v1` JSON
|
||||
envelope by default. There is no `--json` flag because JSON is already the
|
||||
default. Bare slugs remain accepted for compatibility when the skill is
|
||||
already installed or unambiguous, but owner-qualified refs avoid publisher
|
||||
ambiguity.
|
||||
- `verify <slug>` prints ClawHub's `clawhub.skill.verify.v1` JSON envelope by
|
||||
default. There is no `--json` flag because JSON is already the default.
|
||||
- When ClawHub returns server-resolved source provenance, verify JSON also
|
||||
includes a commit-pinned `openclaw.verifiedSourceUrl`. Unavailable or
|
||||
self-declared source URLs stay only in the raw provenance envelope and are not
|
||||
|
||||
@@ -28,7 +28,6 @@ openclaw update --tag main
|
||||
openclaw update --dry-run
|
||||
openclaw update --no-restart
|
||||
openclaw update --yes
|
||||
openclaw update --acknowledge-clawhub-risk
|
||||
openclaw update --json
|
||||
openclaw --update
|
||||
```
|
||||
@@ -46,11 +45,6 @@ openclaw --update
|
||||
when npm plugin artifact drift is detected during post-update plugin sync.
|
||||
- `--timeout <seconds>`: per-step timeout (default is 1800s).
|
||||
- `--yes`: skip confirmation prompts (for example downgrade confirmation).
|
||||
- `--acknowledge-clawhub-risk`: after reviewing community ClawHub trust
|
||||
warnings, allow post-update plugin sync to continue without an interactive
|
||||
prompt. Without this, risky community ClawHub plugin releases are skipped and
|
||||
left unchanged when OpenClaw cannot prompt. Official ClawHub packages and
|
||||
bundled OpenClaw plugin sources bypass this release-trust prompt.
|
||||
|
||||
`openclaw update` does not have a `--verbose` flag. Use `--dry-run` to preview
|
||||
the planned channel/tag/install/restart actions, `--json` for machine-readable
|
||||
@@ -94,7 +88,6 @@ converge.
|
||||
```bash
|
||||
openclaw update repair
|
||||
openclaw update repair --channel beta
|
||||
openclaw update repair --acknowledge-clawhub-risk
|
||||
openclaw update repair --json
|
||||
```
|
||||
|
||||
@@ -105,10 +98,6 @@ Options:
|
||||
- `--json`: print machine-readable finalization JSON.
|
||||
- `--timeout <seconds>`: timeout for repair steps (default `1800`).
|
||||
- `--yes`: skip confirmation prompts.
|
||||
- `--acknowledge-clawhub-risk`: after reviewing community ClawHub trust
|
||||
warnings, allow repair-time plugin convergence to continue without an
|
||||
interactive prompt. Official ClawHub packages and bundled OpenClaw plugin
|
||||
sources bypass this release-trust prompt.
|
||||
- `--no-restart`: accepted for update command parity; repair never restarts the
|
||||
Gateway.
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ openclaw gateway restart
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
openclaw workboard list [--board <id>] [--status <status>] [--include-archived] [--json]
|
||||
openclaw workboard list [--board <id>] [--status <status>] [--json]
|
||||
openclaw workboard create <title...> [--notes <text>] [--status <status>] [--priority <priority>] [--agent <id>] [--board <id>] [--labels <items>] [--json]
|
||||
openclaw workboard show <id> [--json]
|
||||
openclaw workboard dispatch [--url <url>] [--token <token>] [--timeout <ms>] [--json]
|
||||
@@ -50,16 +50,11 @@ Columns are id prefix, status, priority, board id, optional agent id, and title.
|
||||
|
||||
Flags:
|
||||
|
||||
| Flag | Purpose |
|
||||
| -------------------- | --------------------------------------------- |
|
||||
| `--board <id>` | Limit results to one board namespace |
|
||||
| `--status <status>` | Limit results to one Workboard status |
|
||||
| `--include-archived` | Include archived cards in compact text output |
|
||||
| `--json` | Print the full card list as machine JSON |
|
||||
|
||||
Compact text output hides archived cards by default so the CLI matches the
|
||||
`/workboard list` command. Pass `--include-archived` to show them. JSON output
|
||||
keeps the full card list, including archived cards, for existing automation.
|
||||
| Flag | Purpose |
|
||||
| ------------------- | ---------------------------------------- |
|
||||
| `--board <id>` | Limit results to one board namespace |
|
||||
| `--status <status>` | Limit results to one Workboard status |
|
||||
| `--json` | Print the full card list as machine JSON |
|
||||
|
||||
## `create`
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user