mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-15 10:38:52 +08:00
Compare commits
2 Commits
codex/maco
...
fix/codeql
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ea97f43c6 | ||
|
|
faf9ab5f53 |
@@ -16,19 +16,6 @@ warm caches, local build state, and fast feedback.
|
||||
|
||||
Testbox is the expensive path. Reach for it deliberately.
|
||||
|
||||
OpenClaw maintainers can opt into Testbox-first validation by setting
|
||||
`OPENCLAW_TESTBOX=1` in their environment or standing agent rules. This mode is
|
||||
maintainers-only and requires Blacksmith access.
|
||||
|
||||
When `OPENCLAW_TESTBOX=1` is set in OpenClaw:
|
||||
|
||||
- Pre-warm a Testbox early for longer, wider, or uncertain work.
|
||||
- Prefer Testbox for `pnpm` gates, e2e, package-like proof, and broad suites.
|
||||
- Reuse the same Testbox ID for every run command in the same task/session.
|
||||
- Use local commands only when the task explicitly sets
|
||||
`OPENCLAW_LOCAL_CHECK_MODE=throttled|full`, or when the user asks for local
|
||||
proof.
|
||||
|
||||
## Install the CLI
|
||||
|
||||
If `blacksmith` is not installed, install it:
|
||||
@@ -94,8 +81,7 @@ Prefer Testbox when:
|
||||
- you are reproducing CI-only failures
|
||||
- you need the exact workflow image/job environment from GitHub Actions
|
||||
|
||||
For OpenClaw specifically, normal local iteration stays local unless maintainer
|
||||
Testbox mode is enabled with `OPENCLAW_TESTBOX=1`:
|
||||
For OpenClaw specifically, normal local iteration should stay local:
|
||||
|
||||
- `pnpm check:changed`
|
||||
- `pnpm test:changed`
|
||||
@@ -103,54 +89,27 @@ Testbox mode is enabled with `OPENCLAW_TESTBOX=1`:
|
||||
- `pnpm test:serial`
|
||||
- `pnpm build`
|
||||
|
||||
If `OPENCLAW_TESTBOX=1` is enabled, run those same repo commands inside the
|
||||
warm Testbox. If the user wants laptop-friendly local proof for one command, use
|
||||
the explicit escape hatch `OPENCLAW_LOCAL_CHECK_MODE=throttled`.
|
||||
|
||||
For installable-package product proof, prefer the GitHub `Package Acceptance`
|
||||
workflow over an ad hoc Testbox command. It resolves one package candidate
|
||||
(`source=npm`, `source=ref`, `source=url`, or `source=artifact`), uploads it as
|
||||
`package-under-test`, and runs the reusable Docker E2E lanes against that exact
|
||||
tarball on GitHub/Blacksmith runners. Use `workflow_ref` for the trusted
|
||||
workflow/harness code and `package_ref` for the source ref to pack when testing
|
||||
an older trusted branch, tag, or SHA.
|
||||
Only use Testbox in OpenClaw when the user explicitly wants CI-parity or the
|
||||
check truly depends on remote secrets/services that the local repo loop cannot
|
||||
provide.
|
||||
|
||||
## Setup: Warmup before coding
|
||||
|
||||
If you decided Testbox is warranted, warm one up early. This returns an ID
|
||||
instantly and boots the CI environment in the background while you work:
|
||||
If you decided Testbox is actually warranted, warm one up early. This returns
|
||||
an ID instantly and boots the CI environment in the background while you work:
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml
|
||||
# → tbx_01jkz5b3t9...
|
||||
|
||||
Save this ID in the current session. You need it for every `run` command.
|
||||
Treat `blacksmith testbox list` as diagnostics, not a reusable work queue.
|
||||
Listed boxes can be visible at the org/repo level while still being unusable or
|
||||
stale for the current local agent lane.
|
||||
|
||||
For OpenClaw maintainer Testbox mode, pre-warm at the start of longer or wider
|
||||
tasks:
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90
|
||||
pnpm testbox:claim --id <ID>
|
||||
|
||||
Use the build-artifact warmup when e2e/package/build proof benefits from seeded
|
||||
`dist/`, `dist-runtime/`, and build-all caches:
|
||||
|
||||
blacksmith testbox warmup ci-build-artifacts-testbox.yml --ref main --idle-timeout 90
|
||||
pnpm testbox:claim --id <ID>
|
||||
Save this ID. You need it for every `run` command.
|
||||
|
||||
Warmup dispatches a GitHub Actions workflow that provisions a VM with the
|
||||
full CI environment: dependencies installed, services started, secrets
|
||||
injected, and a clean checkout of the repo at the default branch.
|
||||
|
||||
In OpenClaw, raw commit SHAs are not reliable dispatch refs for `warmup --ref`;
|
||||
use a branch or tag. The build-artifact workflow resolves `openclaw@beta` and
|
||||
`openclaw@latest` to SHA cache keys internally.
|
||||
|
||||
Options:
|
||||
|
||||
--ref <branch|tag> Git ref to dispatch against (default: repo's default branch)
|
||||
--ref <branch> Git ref to dispatch against (default: repo's default branch)
|
||||
--job <name> Specific job within the workflow (if it has multiple)
|
||||
--idle-timeout <min> Idle timeout in minutes (default: 30)
|
||||
|
||||
@@ -183,26 +142,6 @@ The `run` command automatically waits for the testbox to become ready if
|
||||
it is still booting, so you can call `run` immediately after warmup without
|
||||
needing to check status first.
|
||||
|
||||
In OpenClaw, prefer the guarded runner wrapper so stale/reused ids fail before
|
||||
the Blacksmith CLI spends time syncing or emits a confusing missing-key error:
|
||||
|
||||
pnpm testbox:run --id <ID> -- "OPENCLAW_TESTBOX=1 pnpm check:changed"
|
||||
|
||||
The wrapper refuses to run when the local per-Testbox key is missing or when the
|
||||
id was not claimed by this OpenClaw checkout with `pnpm testbox:claim --id
|
||||
<ID>`. Treat that as the expected remediation, not as a GitHub account or
|
||||
normal SSH-key problem. A local key alone is not enough; a ready box may still
|
||||
carry stale rsync state from another lane.
|
||||
|
||||
If the agent crashes, the remote box relies on Blacksmith's idle timeout. The
|
||||
local OpenClaw claim marker is not deleted automatically, so the wrapper treats
|
||||
claims older than 12 hours as stale. Override only for intentional long-running
|
||||
work with `OPENCLAW_TESTBOX_CLAIM_TTL_MINUTES=<minutes>`.
|
||||
|
||||
Before spending a broad gate on a manually assembled command, you can also run:
|
||||
|
||||
pnpm testbox:sanity -- --id <ID>
|
||||
|
||||
## Downloading files from a testbox
|
||||
|
||||
Use the `download` command to retrieve files or directories from a running
|
||||
@@ -287,11 +226,6 @@ services, CI-only runners, or reproducibility against the workflow image.
|
||||
|
||||
If the repo says local tests/builds are the normal path, follow the repo.
|
||||
|
||||
OpenClaw maintainer exception: if `OPENCLAW_TESTBOX=1` is set by the user or
|
||||
agent environment, treat Testbox as the normal validation path for this repo.
|
||||
Use `OPENCLAW_LOCAL_CHECK_MODE=throttled|full` as the explicit local escape
|
||||
hatch.
|
||||
|
||||
## When to use
|
||||
|
||||
Use Testbox when:
|
||||
@@ -308,26 +242,18 @@ checks that need parity or remote state.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Decide whether the repo's local loop is the right default. For OpenClaw,
|
||||
`OPENCLAW_TESTBOX=1` makes Testbox the maintainer default.
|
||||
2. If Testbox is warranted, warm up early:
|
||||
`blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90` → save the ID,
|
||||
then `pnpm testbox:claim --id <ID>`
|
||||
1. Decide whether the repo's local loop is the right default.
|
||||
2. Only if Testbox is warranted, warm up early:
|
||||
`blacksmith testbox warmup ci-check-testbox.yml` → save the ID
|
||||
3. Write code while the testbox boots in the background.
|
||||
4. Run the remote command when needed:
|
||||
`pnpm testbox:run --id <ID> -- "OPENCLAW_TESTBOX=1 pnpm check:changed"`
|
||||
`blacksmith testbox run --id <ID> "npm test"`
|
||||
5. If tests fail, fix code and re-run against the same warm box.
|
||||
6. If you changed dependency manifests (package.json, etc.), prepend
|
||||
the install command: `blacksmith testbox run --id <ID> "npm install && npm test"`
|
||||
7. If a narrow PR reports a full sync or the box was reused/expired, sanity
|
||||
check the remote copy before a slow gate:
|
||||
`pnpm testbox:run --id <ID> -- "pnpm testbox:sanity"`.
|
||||
If it reports missing root files or mass tracked deletions, stop the box and
|
||||
warm a fresh one. Use `OPENCLAW_TESTBOX_ALLOW_MASS_DELETIONS=1` only for an
|
||||
intentional large deletion PR.
|
||||
8. If you need artifacts (coverage reports, build outputs, etc.), download them:
|
||||
7. If you need artifacts (coverage reports, build outputs, etc.), download them:
|
||||
`blacksmith testbox download --id <ID> coverage/ ./coverage/`
|
||||
9. Once green, commit and push.
|
||||
8. Once green, commit and push.
|
||||
|
||||
## OpenClaw full test suite
|
||||
|
||||
@@ -342,15 +268,9 @@ Observed full-suite time on Blacksmith Testbox is about 3-4 minutes:
|
||||
- 173-180s on a warmed box
|
||||
- 219s on a fresh 32-vCPU box
|
||||
|
||||
When validating before commit/push in maintainer Testbox mode, run
|
||||
`pnpm check:changed` inside the warmed box first when appropriate, then the full
|
||||
suite with the profile above if broad confidence is needed.
|
||||
|
||||
Run `pnpm testbox:sanity` inside the warmed box before the broad command when
|
||||
the sync looks suspicious. It checks that root files such as `pnpm-lock.yaml`
|
||||
still exist and fails on 200 or more tracked deletions. That catches stale or
|
||||
corrupted rsync state before dependency install or Vitest failures hide the real
|
||||
problem.
|
||||
When validating before commit/push, run `pnpm check:changed` first when
|
||||
appropriate, then the full suite with the profile above if broad confidence is
|
||||
needed.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -404,14 +324,12 @@ timeout is reached). Default timeout is 5m; use `--wait-timeout` for longer
|
||||
blacksmith testbox stop --id <ID>
|
||||
|
||||
Testboxes automatically shut down after being idle (default: 30 minutes).
|
||||
If you need a longer session, increase the timeout at warmup time. For OpenClaw
|
||||
maintainer work, use 90 minutes for long-running sessions:
|
||||
If you need a longer session, increase the timeout at warmup time:
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 90
|
||||
blacksmith testbox warmup ci-build-artifacts-testbox.yml --idle-timeout 90
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 60
|
||||
|
||||
## With options
|
||||
|
||||
blacksmith testbox warmup ci-check-testbox.yml --ref main
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 90
|
||||
blacksmith testbox warmup ci-check-testbox.yml --idle-timeout 60
|
||||
blacksmith testbox run --id <ID> "go test ./..."
|
||||
|
||||
@@ -1,320 +0,0 @@
|
||||
---
|
||||
name: clawsweeper
|
||||
description: "Use for all ClawSweeper work: OpenClaw issue/PR sweep reports, commit-review reports, repair jobs, cloud fix PRs, @clawsweeper maintainer mention commands, trusted ClawSweeper-reviewed autofix/automerge, GitHub Actions monitoring, permissions, gates, and manual backfills."
|
||||
---
|
||||
|
||||
# ClawSweeper
|
||||
|
||||
ClawSweeper lives at `~/Projects/clawsweeper`. It is the one OpenClaw
|
||||
maintenance bot for sweeping, commit review, repair jobs, and guarded fix PRs.
|
||||
Use this skill whenever Peter asks about reports, findings, dispatch health,
|
||||
repair/cloud PR creation, comment commands, automerge, permissions, or gates.
|
||||
|
||||
## Start
|
||||
|
||||
```bash
|
||||
cd ~/Projects/clawsweeper
|
||||
git status --short --branch
|
||||
git pull --ff-only
|
||||
pnpm run build:all
|
||||
```
|
||||
|
||||
Do not overwrite unrelated edits. If the tree is dirty, inspect first and keep
|
||||
read-only report work read-only unless Peter asked to commit.
|
||||
|
||||
## One Bot, One App
|
||||
|
||||
Use the ClawSweeper repo and the `clawsweeper` GitHub App. Use only
|
||||
`CLAWSWEEPER_*` configuration for this automation. Do not use legacy apps,
|
||||
variables, labels, or skills.
|
||||
|
||||
Required app setup:
|
||||
|
||||
- `CLAWSWEEPER_APP_CLIENT_ID`: public app client ID for `clawsweeper`.
|
||||
- `CLAWSWEEPER_APP_PRIVATE_KEY`: private key used only inside
|
||||
`actions/create-github-app-token` steps.
|
||||
- Target app permissions: read target scan context; write issues and pull
|
||||
requests; contents write for report commits, repair branches, and workflow
|
||||
inputs; Actions write on `openclaw/clawsweeper` for comment-router
|
||||
re-review dispatch, workflow dispatch, run cancellation, and self-heal;
|
||||
optional Checks write for commit Check Runs.
|
||||
|
||||
Token boundary:
|
||||
|
||||
- Codex workers do not get mutation credentials.
|
||||
- Review workers run with stripped secret/token env.
|
||||
- Deterministic scripts own comments, labels, branch pushes, PR creation,
|
||||
closes, and merges through short-lived GitHub App tokens.
|
||||
- Merge and write gates default closed.
|
||||
|
||||
## Commit Reports
|
||||
|
||||
Canonical commit reports:
|
||||
|
||||
```text
|
||||
records/<repo-slug>/commits/<40-char-sha>.md
|
||||
```
|
||||
|
||||
Use the lister:
|
||||
|
||||
```bash
|
||||
pnpm commit-reports -- --since 6h
|
||||
pnpm commit-reports -- --since "24 hours ago" --findings
|
||||
pnpm commit-reports -- --since 7d --non-clean
|
||||
pnpm commit-reports -- --repo openclaw/openclaw --author steipete --since 7d
|
||||
pnpm commit-reports -- --since 24h --json
|
||||
```
|
||||
|
||||
Results: `nothing_found`, `findings`, `inconclusive`, `failed`,
|
||||
`skipped_non_code`. One report per SHA; reruns overwrite the SHA-named report.
|
||||
|
||||
Manual rerun/backfill:
|
||||
|
||||
```bash
|
||||
gh workflow run commit-review.yml --repo openclaw/clawsweeper \
|
||||
-f target_repo=openclaw/openclaw \
|
||||
-f commit_sha=<end-sha> \
|
||||
-f before_sha=<start-or-parent-sha> \
|
||||
-f create_checks=false \
|
||||
-f enabled=true
|
||||
```
|
||||
|
||||
Use `create_checks=true` only when Peter explicitly wants target commit Check
|
||||
Runs. Add `-f additional_prompt="..."` for focused one-off review instructions.
|
||||
|
||||
## Sweep Reports
|
||||
|
||||
Issue/PR reports live at:
|
||||
|
||||
```text
|
||||
records/<repo-slug>/items/<number>.md
|
||||
records/<repo-slug>/closed/<number>.md
|
||||
```
|
||||
|
||||
Lead with counts, concrete findings, and report links. Do not post unsolicited
|
||||
GitHub comments from report-reading work. Public surfaces are markdown reports,
|
||||
durable ClawSweeper review comments, and optional checks.
|
||||
|
||||
PR reports include Codex `/review`-style `reviewFindings` with priority,
|
||||
confidence, repository-relative file, and line range. Public PR comments show a
|
||||
short `Review findings:` list when findings exist; full review comments,
|
||||
evidence links, likely owners, and runtime details stay inside the collapsed
|
||||
`Review details` block.
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
pnpm run status
|
||||
pnpm run audit
|
||||
pnpm run reconcile
|
||||
pnpm run apply-decisions -- --dry-run
|
||||
```
|
||||
|
||||
## Create One Repair Job
|
||||
|
||||
Create a job from issue/PR refs and a maintainer prompt:
|
||||
|
||||
```bash
|
||||
pnpm run repair:create-job -- \
|
||||
--repo openclaw/openclaw \
|
||||
--refs 123,456 \
|
||||
--prompt-file /tmp/clawsweeper-prompt.md
|
||||
```
|
||||
|
||||
Create from an existing ClawSweeper report:
|
||||
|
||||
```bash
|
||||
pnpm run repair:create-job -- \
|
||||
--from-report ../clawsweeper/records/openclaw-openclaw/items/123.md
|
||||
```
|
||||
|
||||
The job creator checks for an existing open PR, body match, or remote
|
||||
`clawsweeper/<cluster-id>` branch before writing another job. Use `--dry-run`
|
||||
to inspect. Use `--force` only after deciding the duplicate guard is stale.
|
||||
|
||||
Validate, commit, then dispatch:
|
||||
|
||||
```bash
|
||||
pnpm run repair:validate-job -- jobs/openclaw/inbox/clawsweeper-openclaw-openclaw-123.md
|
||||
pnpm run repair:dispatch -- jobs/openclaw/inbox/clawsweeper-openclaw-openclaw-123.md \
|
||||
--mode autonomous \
|
||||
--runner blacksmith-4vcpu-ubuntu-2404 \
|
||||
--execution-runner blacksmith-16vcpu-ubuntu-2404 \
|
||||
--model gpt-5.5
|
||||
```
|
||||
|
||||
Do not dispatch a just-created job before the job file is committed and pushed;
|
||||
the workflow reads the job path from GitHub.
|
||||
|
||||
## Replacement PRs
|
||||
|
||||
For a useful but uneditable/stale/unsafe source PR, make the maintainer prompt
|
||||
explicit:
|
||||
|
||||
```md
|
||||
Treat #123 as useful source work. If the source branch cannot be safely updated
|
||||
because it is uneditable, stale, draft-only, unmergeable, or unsafe, create a
|
||||
narrow ClawSweeper replacement PR instead of waiting. Preserve the source PR
|
||||
author as co-author, credit the source PR in the replacement PR body, and close
|
||||
only that source PR after the replacement PR is opened.
|
||||
```
|
||||
|
||||
The worker should emit `repair_strategy=replace_uneditable_branch` and list the
|
||||
source PR URL in `source_prs`. The deterministic executor opens or updates
|
||||
`clawsweeper/<cluster-id>`, adds non-bot source authors as `Co-authored-by`
|
||||
trailers, and closes superseded source PRs only after replacement exists.
|
||||
|
||||
## Gates
|
||||
|
||||
Open execution windows intentionally and close them after the run:
|
||||
|
||||
```bash
|
||||
gh variable set CLAWSWEEPER_ALLOW_EXECUTE --repo openclaw/clawsweeper --body 1
|
||||
gh variable set CLAWSWEEPER_ALLOW_FIX_PR --repo openclaw/clawsweeper --body 1
|
||||
gh variable set CLAWSWEEPER_ALLOW_MERGE --repo openclaw/clawsweeper --body 1
|
||||
gh variable set CLAWSWEEPER_ALLOW_AUTOMERGE --repo openclaw/clawsweeper --body 1
|
||||
```
|
||||
|
||||
Reset gates only when Peter asks; the active maintainer window may intentionally
|
||||
leave them at `1`.
|
||||
|
||||
Important gates:
|
||||
|
||||
- `CLAWSWEEPER_ALLOW_EXECUTE`: allows deterministic write lanes.
|
||||
- `CLAWSWEEPER_ALLOW_FIX_PR`: allows branch repair/replacement PRs.
|
||||
- `CLAWSWEEPER_ALLOW_MERGE`: allows merge-capable applicators.
|
||||
- `CLAWSWEEPER_ALLOW_AUTOMERGE`: allows comment-router automerge.
|
||||
- `CLAWSWEEPER_COMMENT_ROUTER_EXECUTE`: lets scheduled comment routing
|
||||
post replies and dispatch repair.
|
||||
|
||||
## Maintainer Mentions
|
||||
|
||||
Prefer `@clawsweeper` comments for all maintainer-facing control. Slash
|
||||
commands still parse as compatibility aliases, but examples and live guidance
|
||||
should use mentions.
|
||||
|
||||
```text
|
||||
@clawsweeper status
|
||||
@clawsweeper re-review
|
||||
@clawsweeper review
|
||||
@clawsweeper fix ci
|
||||
@clawsweeper address review
|
||||
@clawsweeper rebase
|
||||
@clawsweeper autofix
|
||||
@clawsweeper automerge
|
||||
@clawsweeper approve
|
||||
@clawsweeper explain
|
||||
@clawsweeper stop
|
||||
@clawsweeper <question or safe action request>
|
||||
@clawsweeper[bot] re-review
|
||||
@openclaw-clawsweeper fix ci
|
||||
@openclaw-clawsweeper[bot] fix ci
|
||||
```
|
||||
|
||||
Accepted aliases: `review`, `re-review`, `rereview`, `review again`,
|
||||
`rerun review`, and `run review`. `review` and `re-review` dispatch a fresh
|
||||
ClawSweeper issue/PR review without starting repair. `fix ci`,
|
||||
`address review`, and `rebase` dispatch the
|
||||
repair worker only for ClawSweeper PRs or PRs opted into
|
||||
`clawsweeper:autofix` or `clawsweeper:automerge`. `autofix` runs the bounded
|
||||
review/fix loop without merging. `automerge` runs the bounded review/fix/merge
|
||||
loop, but draft PRs stay fix-only until GitHub marks them ready for review.
|
||||
|
||||
Freeform maintainer mentions such as `@clawsweeper why did automerge stop?`
|
||||
or `@clawsweeper: can you explain this failure?` dispatch a read-only assist
|
||||
review with the mention text as one-off instructions. The answer lands in the
|
||||
next public ClawSweeper review comment. Action-looking prose does not directly
|
||||
mutate GitHub; it must map to existing structured recommendations and pass the
|
||||
normal deterministic gates.
|
||||
|
||||
Default accepted maintainers: `OWNER`, `MEMBER`, `COLLABORATOR`; fallback
|
||||
repository permission accepts `admin`, `maintain`, or `write`. Contributor
|
||||
comments are ignored without a reply.
|
||||
|
||||
Run router manually:
|
||||
|
||||
```bash
|
||||
pnpm run repair:comment-router -- --repo openclaw/openclaw --lookback-minutes 180
|
||||
pnpm run repair:comment-router -- --repo openclaw/openclaw --execute --wait-for-capacity
|
||||
```
|
||||
|
||||
Scheduled routing stays dry unless
|
||||
`CLAWSWEEPER_COMMENT_ROUTER_EXECUTE=1`.
|
||||
|
||||
## Trusted Autofix And Automerge
|
||||
|
||||
`@clawsweeper autofix` opts an existing PR into the bounded review/fix loop.
|
||||
`@clawsweeper automerge` opts an existing PR into the bounded review/fix/merge
|
||||
loop. The router:
|
||||
|
||||
- verifies maintainer authorization;
|
||||
- labels the PR `clawsweeper:autofix` or `clawsweeper:automerge`;
|
||||
- dispatches ClawSweeper review for the current head SHA;
|
||||
- creates or reuses a durable adopted job;
|
||||
- repairs at most the configured caps;
|
||||
- never merges autofix PRs or draft PRs;
|
||||
- merges automerge PRs only when ClawSweeper passed the exact current head,
|
||||
checks are green, GitHub says mergeable, no human-review label is present,
|
||||
the PR is not draft, and both merge gates are open.
|
||||
|
||||
If ClawSweeper passes while merge gates are closed, it labels
|
||||
`clawsweeper:merge-ready` and comments instead of merging. `@clawsweeper stop`
|
||||
adds `clawsweeper:human-review`.
|
||||
|
||||
Repair caps:
|
||||
|
||||
```bash
|
||||
CLAWSWEEPER_MAX_REPAIRS_PER_PR=10
|
||||
CLAWSWEEPER_MAX_REPAIRS_PER_HEAD=1
|
||||
```
|
||||
|
||||
## Security Boundary
|
||||
|
||||
Do not stage security-sensitive work for ClawSweeper Repair. Route vulnerability
|
||||
reports, CVE/GHSA/advisory work, leaked secrets/tokens/keys, plaintext secret
|
||||
storage, SSRF, XSS, CSRF, RCE, auth bypass, privilege escalation, and sensitive
|
||||
data exposure to central OpenClaw security handling.
|
||||
|
||||
For adopted automerge jobs, trust deterministic ClawSweeper security markers,
|
||||
labels, and job frontmatter; do not infer security handling from vague prose.
|
||||
|
||||
## Monitoring
|
||||
|
||||
Receiver workflows:
|
||||
|
||||
```bash
|
||||
gh run list --repo openclaw/clawsweeper --workflow "ClawSweeper Commit Review" \
|
||||
--limit 12 --json databaseId,displayTitle,event,status,conclusion,createdAt,updatedAt,url
|
||||
gh run list --repo openclaw/clawsweeper --workflow "repair cluster worker" \
|
||||
--limit 12 --json databaseId,displayTitle,event,status,conclusion,createdAt,updatedAt,url
|
||||
gh run list --repo openclaw/clawsweeper --workflow "repair comment router" \
|
||||
--limit 12 --json databaseId,displayTitle,event,status,conclusion,createdAt,updatedAt,url
|
||||
```
|
||||
|
||||
Target dispatcher:
|
||||
|
||||
```bash
|
||||
gh run list --repo openclaw/openclaw --workflow "ClawSweeper Dispatch" \
|
||||
--event push --limit 8 --json databaseId,displayTitle,event,status,conclusion,headSha,url
|
||||
```
|
||||
|
||||
Target commit check:
|
||||
|
||||
```bash
|
||||
gh api "repos/openclaw/openclaw/commits/<sha>/check-runs?per_page=100" \
|
||||
--jq '.check_runs[] | select(.name=="ClawSweeper Commit Review") | [.status,.conclusion,.details_url] | @tsv'
|
||||
```
|
||||
|
||||
## Reading Output
|
||||
|
||||
For findings or failures, summarize:
|
||||
|
||||
- target repo, item/PR/commit, run, report path
|
||||
- result, confidence, severity, and exact blocker
|
||||
- affected files or cluster refs
|
||||
- validation commands and whether they passed
|
||||
- whether mutation gates were open or closed
|
||||
- next deterministic action
|
||||
|
||||
Keep the broom small: one cluster, one branch, one PR, narrow proof, clear
|
||||
owner-visible evidence.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "ClawSweeper"
|
||||
short_description: "Inspect ClawSweeper commit review reports and Actions runs."
|
||||
default_prompt: "Review recent ClawSweeper commit reports and summarize findings."
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
name: discord-clawd
|
||||
description: Use to talk to the Discord-backed OpenClaw agent/session; not for archive search.
|
||||
---
|
||||
|
||||
# Discord Clawd
|
||||
|
||||
Use this when the task is to talk with the Discord-backed agent/session, ask it a question, or post through that route.
|
||||
|
||||
For Discord archive/history/search, use `$discrawl` instead.
|
||||
|
||||
## Transport
|
||||
|
||||
Use the OpenClaw relay helper:
|
||||
|
||||
```bash
|
||||
cd ~/Projects/agent-scripts
|
||||
python3 skills/openclaw-relay/scripts/openclaw_relay.py targets
|
||||
python3 skills/openclaw-relay/scripts/openclaw_relay.py resolve --target maintainers
|
||||
```
|
||||
|
||||
If the target alias exists, prefer a private ask first:
|
||||
|
||||
```bash
|
||||
python3 skills/openclaw-relay/scripts/openclaw_relay.py ask \
|
||||
--target maintainers \
|
||||
--message "Reply with exactly OK."
|
||||
```
|
||||
|
||||
Use `publish` when the session should decide whether to post. Use `force-send` only when the user explicitly wants a message posted.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Resolve the target before sending real content.
|
||||
- Report the target and delivery mode used.
|
||||
- Do not use this for local Discord archive queries.
|
||||
- Do not expose gateway tokens or session secrets.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "Discord Clawd"
|
||||
short_description: "Talk to the Discord-backed OpenClaw agent"
|
||||
default_prompt: "Use $discord-clawd to route a private ask or explicit post through the Discord-backed OpenClaw agent/session."
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
name: gitcrawl
|
||||
description: Use gitcrawl for OpenClaw issue and PR archive search, duplicate discovery, related-thread clustering, and local GitHub mirror freshness checks.
|
||||
metadata:
|
||||
openclaw:
|
||||
requires:
|
||||
bins:
|
||||
- gitcrawl
|
||||
---
|
||||
|
||||
# Gitcrawl
|
||||
|
||||
Use this skill before live GitHub search when triaging OpenClaw issues or PRs.
|
||||
|
||||
`gitcrawl` is the local candidate-discovery layer. It is fast, includes open and closed threads, and can surface duplicate attempts, related issues, and already-landed fixes. It is not the final source of truth for comments, labels, merges, closes, or current CI.
|
||||
|
||||
## Default Flow
|
||||
|
||||
1. Check local state:
|
||||
|
||||
```bash
|
||||
gitcrawl doctor --json
|
||||
```
|
||||
|
||||
2. Read the target from the local archive:
|
||||
|
||||
```bash
|
||||
gitcrawl threads openclaw/openclaw --numbers <issue-or-pr-number> --include-closed --json
|
||||
```
|
||||
|
||||
3. Find related candidates:
|
||||
|
||||
```bash
|
||||
gitcrawl neighbors openclaw/openclaw --number <issue-or-pr-number> --limit 12 --json
|
||||
gitcrawl search openclaw/openclaw --query "<scope or title keywords>" --mode hybrid --limit 20 --json
|
||||
```
|
||||
|
||||
4. Inspect relevant clusters:
|
||||
|
||||
```bash
|
||||
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --body-chars 280 --json
|
||||
```
|
||||
|
||||
5. Verify anything actionable with live GitHub and the checkout:
|
||||
|
||||
```bash
|
||||
gh pr view <number> --json number,title,state,mergedAt,body,files,comments,reviews,statusCheckRollup
|
||||
gh issue view <number> --json number,title,state,body,comments,closedAt
|
||||
```
|
||||
|
||||
## Freshness Rules
|
||||
|
||||
- Treat `gitcrawl` as stale if `doctor` shows no target thread, an old `last_sync_at`, missing embeddings for neighbor/search commands, or a clearly wrong open/closed state.
|
||||
- If stale data blocks the decision, refresh the portable store first:
|
||||
|
||||
```bash
|
||||
gitcrawl init --portable-store git@github.com:openclaw/gitcrawl-store.git --json
|
||||
```
|
||||
|
||||
- Run expensive update commands such as `gitcrawl sync --include-comments` only when the user asked to update the local store or stale data is blocking the decision.
|
||||
- The sync default is all GitHub thread states; pass `--state open`, `--state closed`, or `--state all` only when a task requires a narrower or explicit scope.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Use `gitcrawl` for candidates, clusters, and historical context.
|
||||
- Use `gh`, `gh api`, and the current checkout for live state before commenting, labeling, closing, reopening, merging, or filing a PR review.
|
||||
- Do not close or label based only on `gitcrawl` similarity. Require matching problem intent plus live verification.
|
||||
- If `gitcrawl` is unavailable, say so and fall back to targeted `gh search` rather than blocking normal maintainer work.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "Gitcrawl"
|
||||
short_description: "Search local OpenClaw issue and PR history before live GitHub triage"
|
||||
default_prompt: "Use $gitcrawl to inspect OpenClaw issue and PR history, find related threads and duplicate candidates, then verify actionable decisions with live GitHub."
|
||||
@@ -7,23 +7,6 @@ description: Review, triage, close, label, comment on, or land OpenClaw PRs/issu
|
||||
|
||||
Use this skill for maintainer-facing GitHub workflow, not for ordinary code changes.
|
||||
|
||||
## Start issue and PR triage with gitcrawl
|
||||
|
||||
- Use `$gitcrawl` first anytime you inspect OpenClaw issues or PRs.
|
||||
- Check local `gitcrawl` data first for related threads, duplicate attempts, and already-landed fixes.
|
||||
- Use `gitcrawl` for candidate discovery and clustering; use `gh`, `gh api`, and the current checkout to verify live state before commenting, labeling, closing, or landing.
|
||||
- If `gitcrawl` is missing, stale, lacks the target thread, or has no embeddings for neighbor/search commands, fall back to the GitHub search workflow below.
|
||||
- Do not run expensive/update commands such as `gitcrawl sync --include-comments`, future enrichment commands, or broad reclustering unless the user asked to update the local store or stale data is blocking the decision.
|
||||
|
||||
Common read-only path:
|
||||
|
||||
```bash
|
||||
gitcrawl threads openclaw/openclaw --numbers <issue-or-pr-number> --include-closed --json
|
||||
gitcrawl neighbors openclaw/openclaw --number <issue-or-pr-number> --limit 12 --json
|
||||
gitcrawl search openclaw/openclaw --query "<scope or title keywords>" --mode hybrid --json
|
||||
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --body-chars 280 --json
|
||||
```
|
||||
|
||||
## Apply close and triage labels correctly
|
||||
|
||||
- If an issue or PR matches an auto-close reason, apply the label and let `.github/workflows/auto-response.yml` handle the comment/close/lock flow.
|
||||
@@ -76,9 +59,9 @@ gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --
|
||||
|
||||
## Search broadly before deciding
|
||||
|
||||
- Prefer `gitcrawl` first. Then use targeted GitHub keyword search to verify gaps, live status, comments, and candidates not present in the local store.
|
||||
- Use `--repo openclaw/openclaw` with `--match title,body` first when using `gh search`.
|
||||
- Add `--match comments` when triaging follow-up discussion or closed-as-duplicate chains.
|
||||
- Prefer targeted keyword search before proposing new work or closing something as duplicate.
|
||||
- Use `--repo openclaw/openclaw` with `--match title,body` first.
|
||||
- Add `--match comments` when triaging follow-up discussion.
|
||||
- Do not stop at the first 500 results when the task requires a full search.
|
||||
|
||||
Examples:
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
---
|
||||
name: openclaw-pre-release-plugin-testing
|
||||
description: Plan and run pre-release OpenClaw plugin validation across bundled plugins, package artifacts, lifecycle commands, doctor/fix, config round-trip, gateway startup, SDK compatibility, Docker E2E, Package Acceptance, and Testbox proof.
|
||||
---
|
||||
|
||||
# OpenClaw Pre-Release Plugin Testing
|
||||
|
||||
Use this skill when the user asks for plugin release confidence, plugin lifecycle
|
||||
sweeps, package-artifact plugin proof, or "what else should we test before
|
||||
release?" It complements `openclaw-testing`; use that skill too when choosing
|
||||
the cheapest safe runner or debugging a failing lane.
|
||||
|
||||
## Goal
|
||||
|
||||
Prove the plugin system as a product surface, not just as source tests:
|
||||
|
||||
- bundled plugin lifecycle: install, inspect, enable, disable, uninstall
|
||||
- package artifact behavior from a clean `HOME`
|
||||
- doctor/fix/config validation and idempotence
|
||||
- config discovery and config round-trip
|
||||
- status/log visibility and diagnostics
|
||||
- gateway startup/bootstrap with plugin metadata snapshots
|
||||
- public SDK compatibility for real external plugins
|
||||
- live-ish provider/channel probes only when safe credentials exist
|
||||
|
||||
## First Checks
|
||||
|
||||
From the OpenClaw repo root:
|
||||
|
||||
```bash
|
||||
pnpm docs:list
|
||||
git status --short --branch
|
||||
readlink node_modules
|
||||
pnpm changed:lanes --json
|
||||
```
|
||||
|
||||
In Codex worktrees under `.codex/worktrees`, `node_modules` must be a symlink to
|
||||
the main OpenClaw checkout. Do not run `pnpm install` there. For broad or
|
||||
package-heavy proof, use Blacksmith Testbox or GitHub Actions.
|
||||
|
||||
## Runner Choice
|
||||
|
||||
Prefer this order:
|
||||
|
||||
1. **GitHub Package Acceptance** for installable-package product proof.
|
||||
2. **`ci-build-artifacts-testbox.yml` Testbox** when Docker/package lanes need
|
||||
seeded `dist`, `dist-runtime`, and package caches.
|
||||
3. **`ci-check-testbox.yml` Testbox** for source checks, targeted Vitest,
|
||||
package-boundary checks, or focused Docker lanes.
|
||||
4. **Local targeted commands only** for small format/static/unit probes.
|
||||
|
||||
Avoid long package Docker runs from a stale sparse worktree. If Testbox sync
|
||||
reports hundreds of changed files or starts deleting package inputs, stop and
|
||||
warm a fresh box from current `main`, or switch to Package Acceptance.
|
||||
|
||||
## Existing Baseline
|
||||
|
||||
Run or verify these before inventing new coverage:
|
||||
|
||||
```bash
|
||||
OPENCLAW_TESTBOX=1 pnpm check:changed
|
||||
pnpm run test:extensions:package-boundary:canary
|
||||
pnpm run test:extensions:package-boundary:compile
|
||||
pnpm test:docker:plugins
|
||||
OPENCLAW_PLUGINS_E2E_CLAWHUB=0 pnpm test:docker:plugins
|
||||
pnpm test:docker:plugin-update
|
||||
pnpm test:docker:bundled-channel-deps:fast
|
||||
```
|
||||
|
||||
For full bundled install/uninstall proof, shard the packaged sweep:
|
||||
|
||||
```bash
|
||||
OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL=8 \
|
||||
OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX=<0-7> \
|
||||
pnpm test:docker:bundled-plugin-install-uninstall
|
||||
```
|
||||
|
||||
Expected current packaged scope: 116 public bundled plugins over shards `0-7`.
|
||||
Private QA plugins are source-mode only unless a package explicitly includes
|
||||
them.
|
||||
|
||||
## Confidence Matrix
|
||||
|
||||
Use this matrix for pre-release signoff. Record pass/fail, run URL/Testbox ID,
|
||||
package SHA/version, and skipped-live reason.
|
||||
|
||||
| Surface | Proof | Preferred runner |
|
||||
| --- | --- | --- |
|
||||
| Package artifact | Package Acceptance `suite_profile=package` or custom lanes | GitHub Actions |
|
||||
| Bundled lifecycle | 8-shard `test:docker:bundled-plugin-install-uninstall` | Testbox or release Docker |
|
||||
| External plugins | `test:docker:plugins` and `plugins-offline` | Testbox/package acceptance |
|
||||
| Update no-op | `test:docker:plugin-update` | Testbox/package acceptance |
|
||||
| Channel runtime deps | `test:docker:bundled-channel-deps:fast` plus key channels | Testbox/package acceptance |
|
||||
| Doctor/fix | seeded bad configs + `doctor --fix --non-interactive` | new Docker/Testbox harness |
|
||||
| Config round-trip | `config set/get`, inspect, doctor, reload, diff hash | new Docker/Testbox harness |
|
||||
| Gateway bootstrap | clean `HOME`, plugin groups enabled/disabled, status JSON | new Docker/Testbox harness |
|
||||
| SDK compatibility | directory, tgz, and `file:` external plugins using SDK subpaths | `test:docker:plugins` plus new smoke |
|
||||
| Live-ish | redacted provider/channel probes only for present env | Testbox live lanes |
|
||||
|
||||
## Package Acceptance Plan
|
||||
|
||||
Use this when validating a release branch, beta, or candidate package:
|
||||
|
||||
```bash
|
||||
gh workflow run package-acceptance.yml \
|
||||
--repo openclaw/openclaw \
|
||||
--ref main \
|
||||
-f workflow_ref=main \
|
||||
-f source=ref \
|
||||
-f package_ref=<branch-or-sha> \
|
||||
-f suite_profile=custom \
|
||||
-f docker_lanes='plugins-offline plugin-update bundled-channel-deps-compat doctor-switch update-channel-switch config-reload mcp-channels npm-onboard-channel-agent' \
|
||||
-f telegram_mode=mock-openai
|
||||
```
|
||||
|
||||
Use `source=npm -f package_spec=openclaw@beta` for published beta proof. Keep
|
||||
`workflow_ref` as trusted current harness code unless the release process says
|
||||
otherwise.
|
||||
|
||||
## New Testbox Harness Plan
|
||||
|
||||
If more certainty is needed, add or run a `plugin-lifecycle-matrix` Docker lane
|
||||
that uses one package tarball and sharded plugin lists. Per plugin:
|
||||
|
||||
1. Start with a clean `HOME`.
|
||||
2. Capture `plugins list --json`.
|
||||
3. `plugins install <id>`.
|
||||
4. `plugins inspect <id> --json`.
|
||||
5. `plugins disable <id>`, then assert disabled visibility.
|
||||
6. `plugins enable <id>`, except config-required plugins without config.
|
||||
7. `plugins registry --refresh`.
|
||||
8. `doctor --non-interactive`.
|
||||
9. `plugins uninstall <id> --force`.
|
||||
10. Assert no config entry, allow/deny residue, install record, managed dir, or
|
||||
bundled `dist/extensions/...` load path remains.
|
||||
11. Assert diagnostics contain no `level: "error"` and output redacts
|
||||
secret-looking values.
|
||||
|
||||
Keep `memory-lancedb` special: it is config-required. First assert install does
|
||||
not enable it without embedding config, then run a second configured case.
|
||||
|
||||
## Doctor/Fix Matrix
|
||||
|
||||
Seed bad states and require `doctor --fix --non-interactive` to repair them,
|
||||
then run doctor again and require idempotence:
|
||||
|
||||
- stale `plugins.allow`
|
||||
- stale `plugins.entries`
|
||||
- stale channel config for missing channel plugin
|
||||
- invalid `plugins.entries.<id>.config`
|
||||
- packaged bundled path in `plugins.load.paths`
|
||||
- legacy `plugins.installs`
|
||||
- disabled channel/plugin config that must not stage runtime deps
|
||||
- root-owned global package tree that must remain unmodified
|
||||
|
||||
## Gateway Bootstrap Matrix
|
||||
|
||||
Start packaged OpenClaw in Docker with clean state:
|
||||
|
||||
- provider plugins enabled, no credentials: ready with warnings, no crash
|
||||
- channel plugins configured disabled: no runtime deps staged
|
||||
- startup-activation plugins enabled: ready and reflected in status
|
||||
- invalid single plugin config: bad plugin skipped/quarantined, others remain
|
||||
|
||||
Assert:
|
||||
|
||||
- gateway reaches ready
|
||||
- `openclaw status --json` includes plugin diagnostics
|
||||
- `openclaw plugins inspect --all --json` is parseable
|
||||
- package tree is not mutated
|
||||
- logs contain no raw tokens
|
||||
|
||||
## Config Round-Trip Representatives
|
||||
|
||||
Use representative plugin families instead of every plugin for deep config
|
||||
round-trip:
|
||||
|
||||
- providers: `openai`, `anthropic`, `mistral`, `openrouter`
|
||||
- channels: `telegram`, `discord`, `slack`, `whatsapp`
|
||||
- memory: `memory-lancedb`
|
||||
- feature/runtime: `browser`, `acpx`, `tokenjuice`
|
||||
|
||||
For each representative:
|
||||
|
||||
1. Write config through CLI when possible.
|
||||
2. Read it back through `config get` or JSON.
|
||||
3. Run `plugins inspect`.
|
||||
4. Run `doctor --non-interactive`.
|
||||
5. Trigger gateway config reload if applicable.
|
||||
6. Compare config hash before/after no-op commands.
|
||||
|
||||
## External SDK Smoke
|
||||
|
||||
In a package Docker lane, create tiny external plugins and install them from:
|
||||
|
||||
- local directory
|
||||
- `.tgz`
|
||||
- `file:` npm spec
|
||||
|
||||
Cover CJS and ESM shapes, plus at least one plugin importing focused
|
||||
`openclaw/plugin-sdk/*` subpaths. Assert `plugins inspect` sees its tool,
|
||||
gateway method, CLI command, or service.
|
||||
|
||||
## Live-Ish Probe Rules
|
||||
|
||||
Before live-ish work, source allowed env in Testbox and generate a redacted
|
||||
availability matrix: present/missing only, never values.
|
||||
|
||||
Only run probes for credentials that exist. Prefer auth/catalog/status probes
|
||||
over sending user-visible messages. If a probe might contact an external user,
|
||||
channel, or workspace, stop and ask the user.
|
||||
|
||||
## Reporting
|
||||
|
||||
Report in this shape:
|
||||
|
||||
```text
|
||||
package/ref:
|
||||
tbx ids / run urls:
|
||||
matrix:
|
||||
bundled lifecycle:
|
||||
package acceptance:
|
||||
doctor/fix:
|
||||
gateway bootstrap:
|
||||
config round-trip:
|
||||
sdk external:
|
||||
live-ish:
|
||||
failures:
|
||||
skips:
|
||||
next highest-value gap:
|
||||
```
|
||||
|
||||
Say clearly when a failure is Testbox sync/env damage rather than product
|
||||
behavior, and prove that with a clean rerun or current-main comparison.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "OpenClaw Plugin Pre-Release Testing"
|
||||
short_description: "Plan plugin release validation"
|
||||
default_prompt: "Use $openclaw-pre-release-plugin-testing to plan or run pre-release OpenClaw plugin validation across package, lifecycle, doctor, gateway, SDK, and live-ish proof."
|
||||
@@ -62,24 +62,6 @@ scenario through qa-channel, decodes the emitted protobuf spans, and verifies
|
||||
the exported trace names and privacy contract. It does not require Opik,
|
||||
Langfuse, or external collector credentials.
|
||||
|
||||
## Matrix live profiles
|
||||
|
||||
`pnpm openclaw qa matrix` defaults to the full `all` profile. Use explicit
|
||||
profiles for faster CI/release proof:
|
||||
|
||||
```bash
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS=3000 \
|
||||
pnpm openclaw qa matrix --profile fast --fail-fast
|
||||
```
|
||||
|
||||
- `fast`: release-critical transport contract, excluding generated image and
|
||||
deep E2EE recovery inventory.
|
||||
- `transport`, `media`, `e2ee-smoke`, `e2ee-deep`, `e2ee-cli`: sharded full
|
||||
Matrix coverage.
|
||||
- `QA-Lab - All Lanes` uses explicit `fast` Matrix on scheduled runs. Manual
|
||||
dispatch keeps `matrix_profile=all` as the default and always shards that full
|
||||
Matrix selection.
|
||||
|
||||
## QA credentials and 1Password
|
||||
|
||||
- Use `op` only inside `tmux` for QA secret lookup in this repo.
|
||||
|
||||
@@ -25,36 +25,15 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
- Before release branching, commit any dirty files in coherent groups, push,
|
||||
pull/rebase, then run `/changelog` on `main` and commit/push/pull that
|
||||
changelog rewrite immediately before creating the release branch.
|
||||
- During release planning, inspect both `src/plugins/compat/registry.ts` and
|
||||
`src/commands/doctor/shared/deprecation-compat.ts` before branching and again
|
||||
before final publish. For every deprecated or removal-pending compatibility
|
||||
record whose `removeAfter` date is on or before the release date, either
|
||||
remove the compatibility path where safe and validate the affected tests, or
|
||||
write down why removal is blocked and get explicit maintainer approval before
|
||||
shipping the expired compatibility path.
|
||||
- When removing deprecated runtime/config compatibility, preserve any doctor
|
||||
migration, repair, or hint that is still needed by supported upgrade paths.
|
||||
Doctor-side compatibility should stay tracked in
|
||||
`src/commands/doctor/shared/deprecation-compat.ts` until maintainers confirm
|
||||
the repair is no longer needed.
|
||||
- Revalidate compatibility replacement text during release planning. The
|
||||
recommended replacement can shift as plugin ownership, externalization, and
|
||||
config footprint move, so do not blindly copy stale replacement annotations
|
||||
into release notes.
|
||||
- Do not delete or rewrite beta tags after they leave the machine. If a
|
||||
published or pushed beta needs a fix, commit the fix on the release branch and
|
||||
increment to the next `-beta.N`.
|
||||
- For a beta release train, run the fast local preflight first, publish the
|
||||
beta to npm `beta`, then run the expensive published-package roster focused
|
||||
on install/update/Docker/Parallels/NPM Telegram. If anything fails, fix it on
|
||||
the release branch, commit/push/pull, increment beta number, and repeat. Run
|
||||
the full expensive roster at least once before stable/latest promotion; for
|
||||
later beta attempts, rerun only lanes whose evidence changed unless the fix
|
||||
touches broad release, install/update, plugin, Docker, Parallels, or live QA
|
||||
behavior. After each beta is published, scan current `main` once for critical
|
||||
fixes that landed after the release branch cut and backport only important
|
||||
low-risk fixes. Operators may authorize up to 4 autonomous beta attempts;
|
||||
after 4 failed beta attempts, stop and report.
|
||||
- For a beta release train, run the full pre-npm test roster before publishing
|
||||
each beta. After a beta is published, run the smaller published-install roster
|
||||
focused on install/update/Docker/Parallels. If anything fails, fix it on the
|
||||
release branch, commit/push/pull, increment beta number, and repeat. Operators
|
||||
may authorize up to 4 autonomous beta attempts; after 4 failed beta attempts,
|
||||
stop and report.
|
||||
- Use `/changelog` before version/tag preparation so the top changelog section
|
||||
is deduped and ordered by user impact.
|
||||
- Do not create beta-specific `CHANGELOG.md` headings. Beta releases use the
|
||||
@@ -96,11 +75,6 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
parallel, publish npm from the successful npm preflight, then start published
|
||||
npm install/update, Docker, and Parallels verification while mac artifacts
|
||||
continue.
|
||||
- After a beta is published, overlap remote/manual release rosters where useful,
|
||||
but avoid piling local Docker, Parallels, and QA-Lab work onto the same host
|
||||
when it would create system-load noise. Use selective reruns after failures or
|
||||
fixes, but keep proof that Docker, Parallels, and QA-Lab each passed at least
|
||||
once before stable/latest promotion.
|
||||
- Mac packaging may be built from a slight release-branch variation of the
|
||||
tagged commit when the delta is mac packaging, signing, workflow, or
|
||||
validation-only release machinery. If mac packaging needs release-branch-only
|
||||
@@ -133,13 +107,6 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
|
||||
or editing a release, extract from `## YYYY.M.D` through the line before the
|
||||
next level-2 heading and use that complete block as the release notes.
|
||||
- When preparing release notes, scan `src/plugins/compat/registry.ts` and
|
||||
`src/commands/doctor/shared/deprecation-compat.ts` for compatibility records
|
||||
with `warningStarts` or `removeAfter` within 7 days after the release date.
|
||||
Add an `Upcoming deprecations` note to the release notes when any exist,
|
||||
including the compatibility code, target date, replacement, and a link to the
|
||||
record's `docsPath` or `/plugins/compatibility` when no more specific
|
||||
deprecation page exists.
|
||||
- When cutting a mac release with a beta GitHub prerelease:
|
||||
- tag `vYYYY.M.D-beta.N` from the release commit
|
||||
- create a prerelease titled `openclaw YYYY.M.D-beta.N`
|
||||
@@ -181,9 +148,6 @@ live`; keep it clearly beta and avoid implying stable promotion.
|
||||
compact launch post, then publish one focused feature explainer per reply.
|
||||
Follow-up replies should not repeat "new in VERSION" or the version number
|
||||
when the thread context already makes it obvious.
|
||||
- Peter's preferred thread workflow: first agree on the generic launch tweet,
|
||||
then proceed through follow-up tweets one by one. When he says `next`, provide
|
||||
or copy the next follow-up only; do not dump the full thread again unless asked.
|
||||
- Every follow-up tweet should include a docs URL for that specific feature.
|
||||
Prefer a bare URL over `Docs: <url>` unless the label is needed for clarity.
|
||||
Keep follow-ups concise: around 160-220 raw characters is usually the sweet
|
||||
@@ -328,11 +292,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Docker install/update coverage that exercises the published beta package
|
||||
- published npm Telegram proof: dispatch Actions > `NPM Telegram Beta E2E`
|
||||
from `main` with `package_spec=openclaw@<beta-version>` and
|
||||
`provider_mode=mock-openai`, and require success. This workflow is
|
||||
maintainer-dispatched and intentionally has no `npm-release` approval gate;
|
||||
`qa-live-shared` only supplies the shared QA secrets. This is the default
|
||||
button path for installed-package onboarding, Telegram setup, and real
|
||||
Telegram E2E against the published npm package.
|
||||
`provider_mode=mock-openai`, approve `npm-release`, and require success.
|
||||
This is the default button path for installed-package onboarding,
|
||||
Telegram setup, and real Telegram E2E against the published npm package.
|
||||
Use the local `pnpm test:docker:npm-telegram-live` lane with the matching
|
||||
`OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC` and Convex CI env only as a fallback
|
||||
or debugging path.
|
||||
@@ -529,10 +491,8 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
6. Create `release/YYYY.M.D` from that post-changelog `main` commit.
|
||||
7. Make every repo version location match the beta tag before creating it.
|
||||
8. Commit release preparation changes on the release branch and push the branch.
|
||||
9. Run the fast local beta preflight from the release branch before any npm
|
||||
preflight or publish. Keep expensive Docker, Parallels, and published-package
|
||||
install/update lanes for after the beta is live unless the operator asks to
|
||||
run them before beta publication.
|
||||
9. Run the local build, Docker, and Parallels parts of the full pre-npm beta
|
||||
test roster from the release branch before any npm preflight or publish.
|
||||
10. For beta releases, skip mac app build/sign/notarize unless beta scope or a
|
||||
release blocker specifically requires it. For stable releases, include the
|
||||
mac app, signing, notarization, and appcast path.
|
||||
@@ -569,16 +529,10 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
21. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
22. Run postpublish verification:
|
||||
`node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>`.
|
||||
23. Run the post-published beta verification roster. First scan current `main`
|
||||
for critical fixes that landed after the release branch cut; backport only
|
||||
important low-risk fixes before starting expensive lanes, or increment to
|
||||
the next beta if the fix must change the already-published package. If any
|
||||
lane fails after the beta tag/package is pushed or published, fix,
|
||||
commit/push/pull, increment to the next beta tag, and rerun the affected
|
||||
beta evidence. Once the beta is live, start remote/manual rosters where they
|
||||
can overlap safely, but keep local Docker and Parallels load controlled.
|
||||
Ensure the full expensive roster has passed at least once before
|
||||
stable/latest promotion. The roster includes the manual Actions >
|
||||
23. Run the post-published beta verification roster. If any lane fails after
|
||||
the beta tag/package is pushed or published, fix, commit/push/pull,
|
||||
increment to the next beta tag, and restart at the full pre-npm beta test
|
||||
roster for the new beta. The roster includes the manual Actions >
|
||||
`NPM Telegram Beta E2E` workflow against the exact published beta package.
|
||||
If a pre-npm lane fails before any tag/package leaves the machine, fix and
|
||||
rerun the same intended beta attempt. Repeat up to the operator's
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
---
|
||||
name: openclaw-test-performance
|
||||
description: Benchmark, diagnose, and optimize OpenClaw test and plugin-suite runtime, import hotspots, CPU/RSS, heap growth, and slow coverage paths.
|
||||
description: Benchmark, diagnose, and optimize OpenClaw test runtime, import hotspots, CPU/RSS, and slow coverage paths.
|
||||
---
|
||||
|
||||
# OpenClaw Test Performance
|
||||
|
||||
Use evidence first. The goal is real `pnpm test`, plugin-suite, and
|
||||
plugin-inspector speed/RSS improvement with coverage intact, not runner tuning by
|
||||
guesswork.
|
||||
Use evidence first. The goal is real `pnpm test` speed/RSS improvement with
|
||||
coverage intact, not runner tuning by guesswork.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -22,9 +21,6 @@ guesswork.
|
||||
2. Establish a baseline before changing code:
|
||||
- Prefer `pnpm test:perf:groups --full-suite --allow-failures --output <file>`
|
||||
for full-suite ranking.
|
||||
- For bundled plugin breadth, run the smallest relevant `pnpm
|
||||
test:extensions:batch <plugin[,plugin...]>` or plugin-inspector command
|
||||
before jumping to the full extension sweep.
|
||||
- For a scoped hotspot use:
|
||||
`/usr/bin/time -l pnpm test <file-or-files> --maxWorkers=1 --reporter=verbose`
|
||||
- For import-heavy suspicion add:
|
||||
@@ -37,8 +33,6 @@ test:extensions:batch <plugin[,plugin...]>` or plugin-inspector command
|
||||
passed, capture that as harness/noise and verify the suspect file directly.
|
||||
4. Pick the next attack by return and risk:
|
||||
- High return: one file/test dominates seconds or RSS and has a clear root.
|
||||
- High leverage: one plugin or SDK barrel causes every plugin-inspector or
|
||||
extension-batch run to load broad runtime.
|
||||
- Lower risk: static descriptors, target parsing, routing, auth bypass,
|
||||
setup hints, registry fixtures, or test server lifecycle.
|
||||
- Higher risk: real memory/runtime behavior, live providers, protocol
|
||||
@@ -50,8 +44,6 @@ test:extensions:batch <plugin[,plugin...]>` or plugin-inspector command
|
||||
and pure helpers over broad mocks.
|
||||
- Reuse suite-level servers/clients when a fresh handshake is irrelevant.
|
||||
- Keep schedulers/background loops off unless the test proves scheduling.
|
||||
- In plugin paths, move static metadata into manifest/lightweight artifacts
|
||||
and keep runtime plugin loads behind explicit execution boundaries.
|
||||
6. Preserve coverage shape:
|
||||
- Do not delete a slow integration proof unless the exact production
|
||||
composition is extracted into a named helper and tested.
|
||||
@@ -65,90 +57,6 @@ test:extensions:batch <plugin[,plugin...]>` or plugin-inspector command
|
||||
9. Commit with `scripts/committer "<message>" <paths...>` and push when the
|
||||
user asked for commits/pushes. Stage only files touched for this attack.
|
||||
|
||||
## Plugin-Suite Workflow
|
||||
|
||||
Use this section when perf work involves bundled plugins, plugin-inspector, SDK
|
||||
barrels, package-boundary tests, or extension suites.
|
||||
|
||||
1. Map the suite shape first:
|
||||
- source tests: `pnpm test extensions/<id>` or `pnpm test:extensions:batch <id>`
|
||||
- package boundaries: `pnpm run test:extensions:package-boundary:canary` and
|
||||
`pnpm run test:extensions:package-boundary:compile`
|
||||
- all bundled source tests: `pnpm test:extensions`
|
||||
- plugin import memory: `pnpm test:extensions:memory -- --json .artifacts/test-perf/extensions-memory.json`
|
||||
- plugin-inspector/report work: keep report primitives in `plugin-inspector`;
|
||||
keep wrappers thin and collect peak RSS when the command supports it.
|
||||
2. Start narrow, then widen:
|
||||
- one plugin changed: run that plugin's tests and plugin-inspector slice.
|
||||
- SDK/public barrel changed: add representative provider, channel, memory,
|
||||
and feature plugins.
|
||||
- loader/runtime mirror changed: add package-boundary checks and build/package
|
||||
proof as needed.
|
||||
- unknown shared plugin behavior: run `test:extensions:batch` groups before
|
||||
`pnpm test:extensions`.
|
||||
3. Treat plugin-inspector failures as product signals:
|
||||
- JSON must parse.
|
||||
- warnings/errors must be classified, not hidden.
|
||||
- runtime capture should be quiet and config-tolerant.
|
||||
- command output should include wall time, exit code, and peak RSS when
|
||||
available.
|
||||
4. For broad or package-heavy plugin proof, use Blacksmith Testbox by default on
|
||||
maintainer machines. Warm once and reuse the same box:
|
||||
- `blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90`
|
||||
- `blacksmith testbox run --id <ID> "OPENCLAW_TESTBOX=1 pnpm test:extensions:batch <ids>"`
|
||||
- stop the box when done.
|
||||
5. If plugin performance is package-artifact sensitive, switch to
|
||||
`openclaw-pre-release-plugin-testing` and Package Acceptance rather than
|
||||
trusting source-only timing.
|
||||
|
||||
## Metric Collection
|
||||
|
||||
Collect at least one stable metric before and after. Prefer the same machine and
|
||||
same command. For Testbox comparisons, use the same `tbx_...` id when possible.
|
||||
|
||||
| Metric | Use for | Preferred source |
|
||||
| --------------- | ---------------------------------- | --------------------------------------------------------------------------- |
|
||||
| wall time | user-visible suite cost | `/usr/bin/time -l`, test wrapper duration, Testbox run time |
|
||||
| Vitest duration | test body/import cost | Vitest output per file/shard |
|
||||
| import duration | broad barrel/runtime loads | `OPENCLAW_VITEST_IMPORT_DURATIONS=1` |
|
||||
| max RSS | memory pressure and OOM risk | `/usr/bin/time -l`, `pnpm test:extensions:memory`, wrapper memory summaries |
|
||||
| CPU/user/sys | CPU-bound vs wait-bound split | `/usr/bin/time -l` locally, Testbox job timing when local CPU is noisy |
|
||||
| heap snapshots | real leak vs retained module graph | `openclaw-test-heap-leaks` workflow |
|
||||
|
||||
Local scoped command with CPU/RSS:
|
||||
|
||||
```bash
|
||||
timeout 240 /usr/bin/time -l pnpm test <file> --maxWorkers=1 --reporter=verbose
|
||||
```
|
||||
|
||||
Plugin import memory profile:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
pnpm test:extensions:memory -- --top 20 --json .artifacts/test-perf/extensions-memory.json
|
||||
```
|
||||
|
||||
Targeted plugin import memory:
|
||||
|
||||
```bash
|
||||
pnpm test:extensions:memory -- --extension discord --extension telegram --skip-combined
|
||||
```
|
||||
|
||||
Heap/RSS escalation:
|
||||
|
||||
```bash
|
||||
OPENCLAW_TEST_MEMORY_TRACE=1 \
|
||||
OPENCLAW_TEST_HEAPSNAPSHOT_INTERVAL_MS=60000 \
|
||||
OPENCLAW_TEST_HEAPSNAPSHOT_DIR=.tmp/heapsnap \
|
||||
OPENCLAW_TEST_WORKERS=2 \
|
||||
OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144 \
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Use `openclaw-test-heap-leaks` when RSS keeps growing across intervals, workers
|
||||
OOM, or the suspect command has app-object retention. Do not call RSS growth a
|
||||
leak until snapshots or retainers support it.
|
||||
|
||||
## Common Root Causes
|
||||
|
||||
- Full bundled channel/plugin runtime loaded for static data.
|
||||
@@ -156,12 +64,6 @@ leak until snapshots or retainers support it.
|
||||
parser would suffice.
|
||||
- Broad `api.ts`, `runtime-api.ts`, `test-api.ts`, or plugin-sdk barrels pulled
|
||||
into hot tests.
|
||||
- SDK root aliases or package barrels pulling focused subpaths back into a broad
|
||||
plugin graph.
|
||||
- Plugin-inspector loading runtime code just to render metadata, reports, or CI
|
||||
policy scores.
|
||||
- Bundled plugin capture reusing real config/home state instead of synthetic,
|
||||
redacted, isolated state.
|
||||
- Partial-real mocks using `importActual()` around broad modules.
|
||||
- `vi.resetModules()` plus fresh imports in per-test loops.
|
||||
- Test plugin registry seeded in `beforeAll` while runtime state resets in
|
||||
@@ -170,10 +72,6 @@ leak until snapshots or retainers support it.
|
||||
- Runtime/default model/auth selection paid by idle snapshots or fixtures.
|
||||
- Plugin-owned media/action discovery triggered before checking whether args
|
||||
contain plugin-owned fields.
|
||||
- Timings missing from `test/fixtures/test-timings.unit.json`, causing hotspot
|
||||
files to stay in shared workers.
|
||||
- Parallel Vitest runs sharing `node_modules/.experimental-vitest-cache` without
|
||||
distinct `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH` values.
|
||||
|
||||
## Benchmark Commands
|
||||
|
||||
@@ -199,25 +97,6 @@ pnpm test:perf:groups --full-suite --allow-failures \
|
||||
--output .artifacts/test-perf/<name>.json
|
||||
```
|
||||
|
||||
Extension batch:
|
||||
|
||||
```bash
|
||||
pnpm test:extensions:batch <plugin[,plugin...]> -- --reporter=verbose
|
||||
```
|
||||
|
||||
All extension tests:
|
||||
|
||||
```bash
|
||||
pnpm test:extensions
|
||||
```
|
||||
|
||||
Package-boundary plugin checks:
|
||||
|
||||
```bash
|
||||
pnpm run test:extensions:package-boundary:canary
|
||||
pnpm run test:extensions:package-boundary:compile
|
||||
```
|
||||
|
||||
Reuse an existing Vitest JSON report:
|
||||
|
||||
```bash
|
||||
@@ -228,26 +107,19 @@ pnpm test:perf:groups --report <vitest-json> \
|
||||
## Verification
|
||||
|
||||
- Always run the targeted test surface that proves the change.
|
||||
- For source changes, run `pnpm check:changed` before push; in maintainer
|
||||
Testbox mode run it in the warmed Testbox.
|
||||
- For test-only changes, run `pnpm test:changed` or the exact edited tests.
|
||||
- Run `pnpm check` before commit unless the change is docs-only and the hook
|
||||
handles it.
|
||||
- Run `pnpm build` when touching lazy-loading, bundled artifacts, package
|
||||
boundaries, dynamic imports, build output, or public surfaces.
|
||||
- For plugin SDK/barrel/runtime changes, add `pnpm plugin-sdk:api:check` or
|
||||
`pnpm plugin-sdk:api:gen` when the API surface may drift.
|
||||
- For plugin-suite perf fixes, verify at least one representative plugin batch
|
||||
plus the changed gate; use Package Acceptance if the bug only exists in a
|
||||
packed artifact.
|
||||
- If deps are missing/stale, run `pnpm install` and retry the exact failed
|
||||
command once.
|
||||
- Use the report format:
|
||||
|
||||
```markdown
|
||||
| Metric | Before | After | Gain |
|
||||
| -------------- | -----: | -----: | ------------: |
|
||||
| File wall time | `Xs` | `Ys` | `-Zs` (`P%`) |
|
||||
| Max RSS | `XMB` | `YMB` | `-ZMB` (`P%`) |
|
||||
| CPU user/sys | `X/Ys` | `A/Bs` | explain |
|
||||
| Metric | Before | After | Gain |
|
||||
| -------------- | -----: | ----: | ------------: |
|
||||
| File wall time | `Xs` | `Ys` | `-Zs` (`P%`) |
|
||||
| Max RSS | `XMB` | `YMB` | `-ZMB` (`P%`) |
|
||||
```
|
||||
|
||||
## Handoff
|
||||
@@ -255,12 +127,8 @@ pnpm test:perf:groups --report <vitest-json> \
|
||||
Keep the final concise:
|
||||
|
||||
- Root cause.
|
||||
- Suite/plugin scope.
|
||||
- Files changed.
|
||||
- Before/after wall, Vitest/import, CPU, and RSS numbers where available.
|
||||
- Leak classification if memory was involved: real leak, retained module graph,
|
||||
or inconclusive.
|
||||
- Before/after numbers.
|
||||
- Coverage retained.
|
||||
- Verification commands.
|
||||
- Testbox ID or workflow URL for remote proof.
|
||||
- Commit hash and push status.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
interface:
|
||||
display_name: "OpenClaw Test Performance"
|
||||
short_description: "Benchmark tests, plugin suites, CPU, RSS, and heap growth"
|
||||
default_prompt: "Use $openclaw-test-performance to reassess OpenClaw test and plugin-suite performance, collect wall/import/CPU/RSS metrics, investigate memory growth when needed, fix the next real hotspot without losing coverage, update the report, and commit scoped changes."
|
||||
short_description: "Benchmark and fix slow OpenClaw tests"
|
||||
default_prompt: "Use $openclaw-test-performance to reassess the OpenClaw test benchmark, identify the next real hotspot, fix it without losing coverage, update the report, and commit scoped changes."
|
||||
policy:
|
||||
allow_implicit_invocation: false
|
||||
|
||||
@@ -1,640 +0,0 @@
|
||||
---
|
||||
name: openclaw-testing
|
||||
description: Choose, run, rerun, or debug OpenClaw tests, CI checks, Docker E2E lanes, release validation, and the cheapest safe verification path.
|
||||
---
|
||||
|
||||
# OpenClaw Testing
|
||||
|
||||
Use this skill when deciding what to test, debugging failures, rerunning CI,
|
||||
or validating a change without wasting hours.
|
||||
|
||||
## Read First
|
||||
|
||||
- `docs/reference/test.md` for local test commands.
|
||||
- `docs/ci.md` for CI scope, release checks, Docker chunks, and runner behavior.
|
||||
- Scoped `AGENTS.md` files before editing code under a subtree.
|
||||
|
||||
## Default Rule
|
||||
|
||||
Prove the touched surface first. Do not reflexively run the whole suite.
|
||||
|
||||
1. Inspect the diff and classify the touched surface:
|
||||
- source: `pnpm changed:lanes --json`, then `pnpm check:changed`
|
||||
- tests only: `pnpm test:changed`
|
||||
- one failing file: `pnpm test <path-or-filter> -- --reporter=verbose`
|
||||
- workflow-only: `git diff --check`, workflow syntax/lint (`actionlint` when available)
|
||||
- docs-only: `pnpm docs:list`, docs formatter/lint only if docs tooling changed or requested
|
||||
2. Reproduce narrowly before fixing.
|
||||
3. Fix root cause.
|
||||
4. Rerun the same narrow proof.
|
||||
5. Broaden only when the touched contract demands it.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Do not kill unrelated processes or tests. If something is running elsewhere, treat it as owned by the user or another agent.
|
||||
- Do not run expensive local Docker, full release checks, full `pnpm test`, or full `pnpm check` unless the user asks or the change genuinely requires it.
|
||||
- Prefer GitHub Actions for release/Docker proof when the workflow already has the prepared image and secrets.
|
||||
- Use `scripts/committer "<msg>" <paths...>` when committing; stage only your files.
|
||||
- If deps are missing, run `pnpm install`, retry once, then report the first actionable error.
|
||||
- For Blacksmith Testbox proof, reuse only an id warmed and claimed in this
|
||||
operator session. `blacksmith testbox list` is diagnostics only; a listed id
|
||||
can have a local key and still carry stale rsync state from another lane.
|
||||
After warmup, run `pnpm testbox:claim --id <id>`, then prefer
|
||||
`pnpm testbox:run --id <id> -- "<command>"` for OpenClaw gates so stale
|
||||
org-visible ids fail fast before syncing. Claims older than 12 hours are
|
||||
stale unless `OPENCLAW_TESTBOX_CLAIM_TTL_MINUTES` is explicitly set for long
|
||||
work.
|
||||
|
||||
## Local Test Shortcuts
|
||||
|
||||
```bash
|
||||
pnpm changed:lanes --json
|
||||
pnpm check:changed # changed typecheck/lint/guards; no Vitest
|
||||
pnpm test:changed # cheap smart changed Vitest targets
|
||||
OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed
|
||||
pnpm test <path-or-filter> -- --reporter=verbose
|
||||
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test <path-or-filter>
|
||||
```
|
||||
|
||||
Use targeted file paths whenever possible. Avoid raw `vitest`; use the repo
|
||||
`pnpm test` wrapper so project routing, workers, and setup stay correct.
|
||||
|
||||
## Command Semantics
|
||||
|
||||
- `pnpm check` and `pnpm check:changed` do not run Vitest tests. They are for
|
||||
typecheck, lint, and guard proof.
|
||||
- `pnpm test` and `pnpm test:changed` run Vitest tests.
|
||||
- `pnpm test:changed` is intentionally cheap by default: direct test edits,
|
||||
sibling tests, explicit source mappings, and import-graph dependents.
|
||||
- `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` is the explicit broad
|
||||
fallback for harness/config/package edits that genuinely need it.
|
||||
- Do not run extension sweeps just because core changed. If a core edit is for a
|
||||
specific plugin bug, run that plugin's tests explicitly. If a public SDK or
|
||||
contract change needs consumer proof, choose the smallest representative
|
||||
plugin/contract tests first, then broaden only when the risk justifies it.
|
||||
- The test wrapper prints a short `[test] passed|failed|skipped ... in ...`
|
||||
line. Vitest's own duration is still the per-shard detail.
|
||||
|
||||
## Routing Model
|
||||
|
||||
- `pnpm changed:lanes --json` answers "which check lanes does this diff touch?"
|
||||
It is used by `pnpm check:changed` for typecheck/lint/guard selection.
|
||||
- `pnpm test:changed` answers "which Vitest targets are worth running now?" It
|
||||
uses the same changed path list, but applies a cheaper test-target resolver.
|
||||
- Direct test edits run themselves. Source edits prefer explicit mappings,
|
||||
sibling `*.test.ts`, then import-graph dependents. Shared harness/config/root
|
||||
edits are skipped by default unless they have precise mapped tests.
|
||||
- Shared group-room delivery config and source-reply prompt edits are precise
|
||||
mapped tests: they run the core auto-reply regressions plus Discord and Slack
|
||||
delivery tests so cross-channel default changes fail before a PR push.
|
||||
- Public SDK or contract edits do not automatically run every plugin test.
|
||||
`check:changed` proves extension type contracts; the agent chooses the
|
||||
smallest plugin/contract Vitest proof that matches the actual risk.
|
||||
- Use `OPENCLAW_TEST_CHANGED_BROAD=1 pnpm test:changed` only when a harness,
|
||||
config, package, or unknown-root edit really needs the broad Vitest fallback.
|
||||
|
||||
## CI Debugging
|
||||
|
||||
Start with current run state, not logs for everything:
|
||||
|
||||
```bash
|
||||
gh run list --branch main --limit 10
|
||||
gh run view <run-id> --json status,conclusion,headSha,url,jobs
|
||||
gh run view <run-id> --job <job-id> --log
|
||||
```
|
||||
|
||||
- Check exact SHA. Ignore newer unrelated `main` unless asked.
|
||||
- For cancelled same-branch runs, confirm whether a newer run superseded it.
|
||||
- Fetch full logs only for failed or relevant jobs.
|
||||
|
||||
## GitHub Release Workflows
|
||||
|
||||
Use the smallest workflow that proves the current risk. The full umbrella is
|
||||
available, but it is usually the last step after narrower proof, not the first
|
||||
rerun after a focused patch.
|
||||
|
||||
### Full Release Validation
|
||||
|
||||
`Full Release Validation` (`.github/workflows/full-release-validation.yml`) is
|
||||
the manual "everything before release" umbrella. It resolves a target ref, then
|
||||
dispatches:
|
||||
|
||||
- manual `CI` for the full normal CI graph, with Android enabled via
|
||||
`include_android=true`
|
||||
- `Plugin Prerelease` for release-only plugin static checks, extension shards,
|
||||
the release-only `agentic-plugins` shard, and plugin product Docker lanes
|
||||
- `OpenClaw Release Checks` for install smoke, cross-OS release checks, live and
|
||||
E2E checks, Docker release-path suites, OpenWebUI, QA Lab, fast Matrix, and
|
||||
Telegram release lanes
|
||||
- optional post-publish Telegram E2E when a package spec is supplied
|
||||
|
||||
Run it only when validating an actual release candidate, after broad shared CI
|
||||
or release orchestration changes, or when explicitly asked:
|
||||
|
||||
```bash
|
||||
gh workflow run full-release-validation.yml \
|
||||
--repo openclaw/openclaw \
|
||||
--ref main \
|
||||
-f ref=<branch-or-sha> \
|
||||
-f provider=openai \
|
||||
-f mode=both \
|
||||
-f release_profile=stable
|
||||
```
|
||||
|
||||
Run the workflow itself from the trusted current ref, normally `--ref main`;
|
||||
child workflows are dispatched from that same ref even when `ref` points at an
|
||||
older release branch or tag. Full Release Validation has no separate child
|
||||
workflow ref input; choose the trusted harness by choosing the workflow run ref.
|
||||
Use `release_profile=minimum|stable|full` to control live/provider breadth:
|
||||
`minimum` keeps the fastest OpenAI/core release-critical set, `stable` adds the
|
||||
stable provider/backend set, and `full` adds the broad advisory provider/media
|
||||
matrix. Do not make `full` faster by silently dropping suites; optimize setup,
|
||||
artifact reuse, and sharding instead. The parent verifier job appends a child
|
||||
overview plus slowest-job tables for child runs; rerun only that verifier after
|
||||
a child rerun turns green.
|
||||
|
||||
Standalone manual `CI` dispatches do not run the plugin prerelease suite, the
|
||||
extension batch sweep, or the release-only `agentic-plugins` Vitest shard. Those
|
||||
lanes are intentionally reserved for the separate `Plugin Prerelease` child so
|
||||
PRs, main pushes, and ad hoc broad CI checks do not spend Docker/package time or
|
||||
all-plugin runtime time on release-only product coverage.
|
||||
|
||||
If a full run is already active on a newer `origin/main`, prefer watching that
|
||||
run over dispatching a duplicate. Do not cancel release, release-check, or child
|
||||
workflow runs unless Peter explicitly asks for cancellation.
|
||||
|
||||
The child-dispatch jobs record the child run ids. The final
|
||||
`Verify full validation` job re-queries those child runs and is the canonical
|
||||
parent gate. If a child workflow failed but was later rerun successfully, rerun
|
||||
only the failed parent verifier job; do not dispatch a new full umbrella unless
|
||||
the release evidence is stale.
|
||||
|
||||
For bounded recovery after a focused fix, pass `-f rerun_group=<group>`.
|
||||
Supported umbrella groups are `all`, `ci`, `plugin-prerelease`,
|
||||
`release-checks`, `install-smoke`, `cross-os`, `live-e2e`, `package`, `qa`,
|
||||
`qa-parity`, `qa-live`, and `npm-telegram`. Use the narrowest group that covers
|
||||
the failed box. After a targeted release-check fix, do not restart the full
|
||||
umbrella by habit: dispatch the matching `rerun_group` and rerun only the parent
|
||||
verifier/evidence step after the child is green unless the release evidence is
|
||||
stale. For a single failed live/E2E shard, use
|
||||
`-f rerun_group=live-e2e -f live_suite_filter=<suite_id>` so the Blacksmith
|
||||
workflow only spends setup and queue time on that suite.
|
||||
|
||||
### Release Evidence
|
||||
|
||||
After release-candidate validation or before a release decision, record the
|
||||
important run ids in the private `openclaw/releases-private` evidence ledger.
|
||||
Use the manual `OpenClaw Release Evidence`
|
||||
(`openclaw-release-evidence.yml`) workflow there. It writes durable summaries
|
||||
under `evidence/<release-id>/` and commits:
|
||||
|
||||
- `release-evidence.md`
|
||||
- `release-evidence.json`
|
||||
- `index.json`
|
||||
- `runs/<label>.json`
|
||||
|
||||
Use one run per line:
|
||||
|
||||
```text
|
||||
full-release-validation openclaw/openclaw <run-id> blocking
|
||||
package-acceptance openclaw/openclaw <run-id> blocking
|
||||
release-checks openclaw/openclaw <run-id> blocking
|
||||
```
|
||||
|
||||
Store summaries, run URLs, artifact metadata, timings, pass/fail state, and
|
||||
short release-manager notes there. Do not store raw logs, provider
|
||||
prompts/responses, channel transcripts, signing material, or secret-bearing
|
||||
config in git; raw logs stay in Actions artifacts.
|
||||
|
||||
When `Full Release Validation` completes and
|
||||
`OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN` is configured in the public repo, it
|
||||
requests the private `OpenClaw Release Evidence From Full Validation` workflow.
|
||||
That private workflow reads the parent full-validation run, extracts the child
|
||||
CI/release-checks/Telegram run ids from the parent logs, and opens the evidence
|
||||
PR automatically. If the token is absent or the run predates this wiring, trigger
|
||||
that private workflow manually with the full-validation run id.
|
||||
|
||||
### Release Checks
|
||||
|
||||
`OpenClaw Release Checks` (`openclaw-release-checks.yml`) is the release child
|
||||
workflow. It is broader than normal CI but narrower than the umbrella because it
|
||||
does not dispatch the separate full normal CI child. It runs Package Acceptance
|
||||
with artifact-native delta lanes and `telegram_mode=mock-openai`, so the release
|
||||
package tarball also goes through offline plugin proof, bundled-channel compat,
|
||||
and Telegram package QA. The Docker release-path chunks cover the overlapping
|
||||
package/update/plugin lanes. Use it when release-path validation is needed
|
||||
without rerunning the entire umbrella.
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-release-checks.yml \
|
||||
--repo openclaw/openclaw \
|
||||
--ref main \
|
||||
-f ref=<branch-or-sha> \
|
||||
-f provider=openai \
|
||||
-f mode=both \
|
||||
-f release_profile=stable \
|
||||
-f rerun_group=all
|
||||
```
|
||||
|
||||
Release-check rerun groups are `all`, `install-smoke`, `cross-os`, `live-e2e`,
|
||||
`package`, `qa`, `qa-parity`, and `qa-live`.
|
||||
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the selected
|
||||
ref once as `release-package-under-test` and passes that artifact into cross-OS
|
||||
release checks, release-path Docker live/E2E checks, and Package Acceptance.
|
||||
When `Full Release Validation` dispatches release checks, it passes the requested
|
||||
branch/tag plus an `expected_sha` so branch/tag refs resolve through the fast
|
||||
remote-ref path while the package and QA jobs still validate the exact SHA.
|
||||
|
||||
The full install-smoke child is split on purpose: one job prepares or reuses the
|
||||
target-SHA GHCR root Dockerfile smoke image, QR package install runs in its own
|
||||
job, root Dockerfile/gateway smokes pull the prepared image, and installer/Bun
|
||||
smokes pull the same image while building only their small installer images.
|
||||
If install-smoke gets slow again, first check whether the root image was reused
|
||||
or rebuilt before adding/removing coverage.
|
||||
|
||||
The full-profile native live media shards use the prebuilt
|
||||
`ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04` container so
|
||||
`ffmpeg`/`ffprobe` are already present. If those jobs suddenly spend minutes in
|
||||
dependency setup again, first check the `Live Media Runner Image` workflow and
|
||||
the `Verify preinstalled live media dependencies` step before assuming the media
|
||||
tests themselves slowed down.
|
||||
|
||||
The release Docker path intentionally shards the plugin/runtime tail. The
|
||||
workflow uses `plugins-runtime-plugins`, `plugins-runtime-services`, and
|
||||
`plugins-runtime-install-a` through `plugins-runtime-install-d`; aggregate
|
||||
aliases such as `plugins-runtime-core`, `plugins-runtime`, and
|
||||
`plugins-integrations` remain for manual reruns.
|
||||
|
||||
The release QA parity box is internally split into candidate and baseline lane
|
||||
jobs, followed by a report job that downloads both artifacts and runs
|
||||
`pnpm openclaw qa parity-report`. For parity failures, inspect the failed lane
|
||||
first; inspect the report job when both lane summaries exist but the comparison
|
||||
fails.
|
||||
|
||||
### QA Lab Matrix Profiles
|
||||
|
||||
`pnpm openclaw qa matrix` defaults to `--profile all`. Do not assume the CLI
|
||||
default is the fast release path. Use explicit profiles:
|
||||
|
||||
- `--profile fast`: release-critical Matrix transport contract; add
|
||||
`--fail-fast` only when the target CLI supports it
|
||||
- `--profile transport|media|e2ee-smoke|e2ee-deep|e2ee-cli`: sharded full
|
||||
Matrix proof
|
||||
- `OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS=3000`: CI-friendly no-reply quiet
|
||||
window when paired with fast or sharded gates
|
||||
|
||||
`QA-Lab - All Lanes` uses explicit fast Matrix on scheduled runs; manual
|
||||
dispatch keeps `matrix_profile=all` as the default and always shards that full
|
||||
Matrix selection. `OpenClaw Release Checks` uses explicit fast Matrix; run the
|
||||
all-lanes workflow when release investigation needs full Matrix media/E2EE
|
||||
inventory.
|
||||
|
||||
### Reusable Live/E2E Checks
|
||||
|
||||
`OpenClaw Live And E2E Checks (Reusable)`
|
||||
(`openclaw-live-and-e2e-checks-reusable.yml`) is the preferred entry point for
|
||||
targeted live, Docker, model, and E2E proof. Inputs let you turn off unrelated
|
||||
lanes:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-live-and-e2e-checks-reusable.yml \
|
||||
--repo openclaw/openclaw \
|
||||
--ref main \
|
||||
-f ref=<sha> \
|
||||
-f include_repo_e2e=false \
|
||||
-f include_release_path_suites=false \
|
||||
-f include_openwebui=false \
|
||||
-f include_live_suites=true \
|
||||
-f live_models_only=true \
|
||||
-f live_model_providers=fireworks
|
||||
```
|
||||
|
||||
Useful knobs:
|
||||
|
||||
- `docker_lanes='<lane[,lane]>'`: run selected Docker scheduler lanes against
|
||||
prepared artifacts instead of the release chunk matrix. Multiple selected
|
||||
lanes fan out as parallel targeted Docker jobs after one shared package/image
|
||||
preparation step.
|
||||
- `include_live_suites=false`: skip live/provider suites when testing Docker
|
||||
scheduler or release packaging only.
|
||||
- `live_models_only=true`: run only Docker live model coverage.
|
||||
- `live_model_providers=fireworks` (or comma/space separated providers): run one
|
||||
targeted Docker live model job instead of the full provider matrix.
|
||||
- blank `live_model_providers`: run the full live-model provider matrix.
|
||||
|
||||
Release-path Docker chunks are currently `core`, `package-update-openai`,
|
||||
`package-update-anthropic`, `package-update-core`,
|
||||
`plugins-runtime-plugins`, `plugins-runtime-services`,
|
||||
`plugins-runtime-install-a`, `plugins-runtime-install-b`,
|
||||
`plugins-runtime-install-c`, `plugins-runtime-install-d`,
|
||||
`bundled-channels-core`, `bundled-channels-update-a`,
|
||||
`bundled-channels-update-b`, and `bundled-channels-contracts`. The aggregate
|
||||
`bundled-channels`, `plugins-runtime-core`, `plugins-runtime`, and
|
||||
`plugins-integrations` chunks remain valid for manual one-shot reruns, but
|
||||
release checks use the split chunks.
|
||||
|
||||
When live suites are enabled, the workflow shards broad native `pnpm test:live`
|
||||
coverage through `scripts/test-live-shard.mjs` instead of one serial `live-all`
|
||||
job:
|
||||
|
||||
- `native-live-src-agents`
|
||||
- `native-live-src-gateway-core`
|
||||
- `native-live-src-gateway-profiles` (release CI runs this with provider
|
||||
filters such as `OPENCLAW_LIVE_GATEWAY_PROVIDERS=anthropic`)
|
||||
- `native-live-src-gateway-backends`
|
||||
- `native-live-test`
|
||||
- `native-live-extensions-a-k`
|
||||
- `native-live-extensions-l-n`
|
||||
- `native-live-extensions-openai`
|
||||
- `native-live-extensions-o-z`
|
||||
- `native-live-extensions-o-z-other`
|
||||
- `native-live-extensions-xai`
|
||||
- `native-live-extensions-media`
|
||||
- `native-live-extensions-media-audio`
|
||||
- `native-live-extensions-media-music`
|
||||
- `native-live-extensions-media-music-google`
|
||||
- `native-live-extensions-media-music-minimax`
|
||||
- `native-live-extensions-media-video`
|
||||
|
||||
Use `node scripts/test-live-shard.mjs <shard> --list` to see the exact files
|
||||
before rerunning a failed native live shard. The aggregate `o-z` and `media`
|
||||
shards remain useful locally; release CI uses the smaller provider/media shards
|
||||
so one live-provider flake does not force a broad native live rerun.
|
||||
|
||||
For model-list or provider-selection fixes, use `live_models_only=true` plus the
|
||||
specific `live_model_providers` allowlist. Confirm logs show the expected
|
||||
`OPENCLAW_LIVE_PROVIDERS` and selected model ids before declaring proof.
|
||||
|
||||
## Docker
|
||||
|
||||
Docker is expensive. First inspect the scheduler without running Docker:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DOCKER_ALL_DRY_RUN=1 pnpm test:docker:all
|
||||
OPENCLAW_DOCKER_ALL_DRY_RUN=1 OPENCLAW_DOCKER_ALL_LANES=install-e2e pnpm test:docker:all
|
||||
OPENCLAW_DOCKER_ALL_LANES=install-e2e node scripts/test-docker-all.mjs --plan-json
|
||||
```
|
||||
|
||||
Run one failed lane locally only when explicitly asked or when GitHub is not
|
||||
usable:
|
||||
|
||||
```bash
|
||||
OPENCLAW_DOCKER_ALL_LANES=<lane> \
|
||||
OPENCLAW_DOCKER_ALL_BUILD=0 \
|
||||
OPENCLAW_DOCKER_ALL_PREFLIGHT=0 \
|
||||
OPENCLAW_SKIP_DOCKER_BUILD=1 \
|
||||
OPENCLAW_DOCKER_E2E_BARE_IMAGE='<prepared-bare-image>' \
|
||||
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE='<prepared-functional-image>' \
|
||||
pnpm test:docker:all
|
||||
```
|
||||
|
||||
For release validation, prefer the reusable GitHub workflow input:
|
||||
|
||||
```yaml
|
||||
docker_lanes: install-e2e
|
||||
```
|
||||
|
||||
Multiple lanes are allowed:
|
||||
|
||||
```yaml
|
||||
docker_lanes: install-e2e bundled-channel-update-acpx
|
||||
```
|
||||
|
||||
That skips the release chunk matrix and runs one targeted Docker job against the
|
||||
prepared GHCR images and the selected package artifact. Rerun commands
|
||||
generated inside GitHub artifacts include `package_artifact_run_id`,
|
||||
`package_artifact_name`, `docker_e2e_bare_image`, and
|
||||
`docker_e2e_functional_image` when available, so failed lanes can reuse the
|
||||
exact tarball and prepared images from the failed run. When the fix changes
|
||||
package contents, omit those reuse inputs so the workflow packs a new tarball.
|
||||
Live-only targeted reruns skip the E2E images and build only the live-test
|
||||
image. Release-path normal mode fans out into smaller Docker chunk jobs:
|
||||
|
||||
- `core`
|
||||
- `package-update-openai`
|
||||
- `package-update-anthropic`
|
||||
- `package-update-core`
|
||||
- `plugins-runtime-plugins`
|
||||
- `plugins-runtime-services`
|
||||
- `plugins-runtime-install-a`
|
||||
- `plugins-runtime-install-b`
|
||||
- `plugins-runtime-install-c`
|
||||
- `plugins-runtime-install-d`
|
||||
- `bundled-channels`
|
||||
|
||||
OpenWebUI is folded into `plugins-runtime-services` for full release-path
|
||||
coverage and keeps a standalone `openwebui` chunk only for OpenWebUI-only
|
||||
dispatches. The legacy `package-update`, `plugins-runtime-core`,
|
||||
`plugins-runtime`, and `plugins-integrations` chunks still work as aggregate
|
||||
aliases for manual reruns, but the release workflow uses the split chunks so
|
||||
provider installer checks, plugin runtime checks, bundled plugin
|
||||
install/uninstall shards, and bundled-channel checks can run on separate
|
||||
machines. The bundled-channel runtime-dependency coverage
|
||||
inside `bundled-channels`
|
||||
uses the split `bundled-channel-*` and `bundled-channel-update-*` lanes rather
|
||||
than the serial `bundled-channel-deps` lane, so failures produce cheap targeted
|
||||
reruns for the exact channel/update scenario. The bundled plugin
|
||||
install/uninstall sweep is also split into
|
||||
`bundled-plugin-install-uninstall-0` through
|
||||
`bundled-plugin-install-uninstall-7`; selecting the legacy
|
||||
`bundled-plugin-install-uninstall` lane expands to all eight shards.
|
||||
|
||||
## Package Acceptance
|
||||
|
||||
Use the manual `Package Acceptance` workflow when the question is "does this
|
||||
installable package work as a product?" rather than "does this source diff pass
|
||||
Vitest?"
|
||||
|
||||
In release validation, treat Package Acceptance as the package-candidate shard
|
||||
inside the larger release umbrella, not as a competing full-test path. Full
|
||||
Release Validation and private release gauntlets should call Package Acceptance
|
||||
for tarball resolution, Docker product/package proof, and optional Telegram QA
|
||||
against the same resolved `package-under-test` artifact; keep orchestration,
|
||||
secret policy, blocking/advisory status, and evidence rollup in the caller.
|
||||
|
||||
Good defaults:
|
||||
|
||||
```bash
|
||||
gh workflow run package-acceptance.yml --ref main \
|
||||
-f source=npm \
|
||||
-f workflow_ref=main \
|
||||
-f package_spec=openclaw@beta \
|
||||
-f suite_profile=product \
|
||||
-f telegram_mode=mock-openai
|
||||
```
|
||||
|
||||
Npm candidate selection:
|
||||
|
||||
- Resolve the registry immediately before dispatch:
|
||||
`npm view openclaw dist-tags --json --prefer-online --cache /tmp/openclaw-npm-cache-verify-$$`
|
||||
and `npm view openclaw@beta version dist.tarball dist.integrity --json --prefer-online --cache /tmp/openclaw-npm-cache-verify-$$`.
|
||||
- If Peter asks for "latest beta", use `source=npm` with
|
||||
`package_spec=openclaw@beta`, then record the resolved version from `npm view`
|
||||
or the workflow summary.
|
||||
- For reruns, release proof, or comparing one known package, prefer the exact
|
||||
immutable spec: `package_spec=openclaw@YYYY.M.D-beta.N` or
|
||||
`package_spec=openclaw@YYYY.M.D`.
|
||||
- For stable package proof, use `package_spec=openclaw@latest` only when the
|
||||
question is explicitly the current stable dist-tag; otherwise pin the exact
|
||||
version.
|
||||
- `source=npm` only accepts registry specs for `openclaw@beta`,
|
||||
`openclaw@latest`, or exact OpenClaw release versions. Do not pass semver
|
||||
ranges, git refs, file paths, tarball URLs, or plugin package names there.
|
||||
- If the candidate is a tarball URL, use `source=url` with `package_sha256`. If
|
||||
it is an Actions tarball artifact, use `source=artifact`. If it is an
|
||||
unpublished source candidate, use `source=ref` with a trusted ref or SHA.
|
||||
- Package acceptance tests exactly the selected package candidate. Do not apply
|
||||
`openclaw update --channel beta` fallback semantics here; if `beta` is absent,
|
||||
stale, older than `latest`, or points at a broken tarball, report that tag
|
||||
state instead of silently testing `latest`.
|
||||
|
||||
Profiles:
|
||||
|
||||
- `smoke`: quick confidence that the tarball installs, can onboard a channel,
|
||||
can run an agent turn, and basic gateway/config lanes work.
|
||||
- `package`: release-package contract. Adds installer/update, doctor install
|
||||
switching, bundled plugin runtime deps, plugin install/update, and package
|
||||
repair lanes. This is the default native replacement for most Parallels
|
||||
package/update coverage.
|
||||
- `product`: package profile plus broader product surfaces: MCP channels,
|
||||
cron/subagent cleanup, OpenAI web search, and OpenWebUI.
|
||||
- `full`: split Docker release-path chunks with OpenWebUI.
|
||||
- `custom`: exact `docker_lanes` list for a focused rerun.
|
||||
|
||||
Candidate sources:
|
||||
|
||||
- `source=npm`: `openclaw@beta`, `openclaw@latest`, or an exact release version.
|
||||
- `source=ref`: pack `package_ref` using the trusted `workflow_ref` harness.
|
||||
This intentionally separates old package commits from new workflow/test code.
|
||||
- `source=url`: HTTPS `.tgz` plus required `package_sha256`.
|
||||
- `source=artifact`: download one `.tgz` from `artifact_run_id`/`artifact_name`.
|
||||
|
||||
Ref model:
|
||||
|
||||
- `gh workflow run ... --ref <workflow-ref>` selects the workflow file revision
|
||||
GitHub executes.
|
||||
- `workflow_ref` is the trusted harness/script ref passed to reusable Docker
|
||||
E2E.
|
||||
- `package_ref` is the source ref to build when `source=ref`. It can be an
|
||||
older branch/tag/SHA as long as it is reachable from an OpenClaw branch or
|
||||
release tag.
|
||||
|
||||
Example: run latest package acceptance harness against an older trusted commit:
|
||||
|
||||
```bash
|
||||
gh workflow run package-acceptance.yml --ref main \
|
||||
-f workflow_ref=main \
|
||||
-f source=ref \
|
||||
-f package_ref=<branch-or-sha> \
|
||||
-f suite_profile=package \
|
||||
-f telegram_mode=mock-openai
|
||||
```
|
||||
|
||||
Use `telegram_mode=mock-openai` or `telegram_mode=live-frontier` when the same
|
||||
resolved `package-under-test` tarball should also run through the Telegram QA
|
||||
workflow in the `qa-live-shared` environment. The standalone Telegram workflow
|
||||
still accepts a published npm spec for post-publish checks, but Package
|
||||
Acceptance passes the resolved artifact for `source=npm`, `ref`, `url`, and
|
||||
`artifact`. Use `telegram_mode=none` only when intentionally skipping Telegram
|
||||
credentialed package proof for a focused rerun.
|
||||
|
||||
Docker E2E images never copy repo sources as the app under test: the bare image
|
||||
is a Node/Git runner, and the functional image installs the same prebuilt npm
|
||||
tarball that bare lanes mount. `scripts/package-openclaw-for-docker.mjs` is the
|
||||
single packer for local scripts and CI and validates the tarball inventory
|
||||
before Docker consumes it. `scripts/test-docker-all.mjs --plan-json` is the
|
||||
scheduler-owned CI plan for image kind, package, live image, lane, and
|
||||
credential needs. Docker lane definitions live in the single scenario catalog
|
||||
`scripts/lib/docker-e2e-scenarios.mjs`; planner logic lives in
|
||||
`scripts/lib/docker-e2e-plan.mjs`. `scripts/docker-e2e.mjs` converts plan and
|
||||
summary JSON into GitHub outputs and step summaries. Every scheduler run writes
|
||||
`.artifacts/docker-tests/**/summary.json` plus `failures.json`. Read those
|
||||
before rerunning. Lane entries include `command`, `rerunCommand`, status,
|
||||
timing, timeout state, image kind, and log file path. The summary also includes
|
||||
top-level phase timings for preflight, image build, package prep, lane pools,
|
||||
and cleanup. Use `pnpm test:docker:timings <summary.json>` to rank slow lanes
|
||||
and phases before deciding whether a broader rerun is justified.
|
||||
|
||||
## Cheap Docker Reruns
|
||||
|
||||
First derive the smallest rerun command from artifacts:
|
||||
|
||||
```bash
|
||||
pnpm test:docker:rerun <github-run-id>
|
||||
pnpm test:docker:rerun .artifacts/docker-tests/<run>/failures.json
|
||||
```
|
||||
|
||||
The script downloads Docker E2E artifacts for a GitHub run, reads
|
||||
`summary.json`/`failures.json`, and prints a combined targeted workflow command
|
||||
plus per-lane commands. Prefer the combined targeted command when several lanes
|
||||
failed for the same patch:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-live-and-e2e-checks-reusable.yml \
|
||||
-f ref=<sha> \
|
||||
-f include_repo_e2e=false \
|
||||
-f include_release_path_suites=false \
|
||||
-f include_openwebui=false \
|
||||
-f docker_lanes='install-e2e bundled-channel-update-acpx' \
|
||||
-f include_live_suites=false \
|
||||
-f live_models_only=false
|
||||
```
|
||||
|
||||
That path still runs the prepare job, so it creates a new tarball for `<sha>`.
|
||||
If the SHA-tagged GHCR bare/functional image already exists, CI skips rebuilding
|
||||
that image and only uploads the fresh package artifact before the targeted lane
|
||||
job. Do not rerun the full release path unless the failed lane list
|
||||
or touched surface really requires it.
|
||||
|
||||
## Docker Expected Timings
|
||||
|
||||
Treat these as ballpark. Blacksmith queue time, GHCR pull speed, provider
|
||||
latency, npm cache state, and Docker daemon health can dominate.
|
||||
|
||||
Current local timing artifact (`.artifacts/docker-tests/lane-timings.json`) has
|
||||
these rough bands:
|
||||
|
||||
- Tiny lanes, seconds to under 1 minute:
|
||||
`agents-delete-shared-workspace` ~3s, `plugin-update` ~7s,
|
||||
`config-reload` ~14s, `pi-bundle-mcp-tools` ~15s, `onboard` ~18s,
|
||||
`session-runtime-context` ~20s, `gateway-network` ~34s, `qr` ~44s.
|
||||
- Medium deterministic lanes, ~1-5 minutes:
|
||||
`npm-onboard-channel-agent` ~96s, `openai-image-auth` ~99s,
|
||||
bundled channel/update lanes usually ~90-300s when split, `openwebui` ~225s,
|
||||
`mcp-channels` ~274s.
|
||||
- Heavy deterministic lanes, ~6-10 minutes:
|
||||
`bundled-channel-root-owned` ~429s,
|
||||
`bundled-channel-setup-entry` ~420s,
|
||||
`bundled-channel-load-failure` ~383s,
|
||||
`cron-mcp-cleanup` ~567s.
|
||||
- Live provider lanes, often ~15-20 minutes:
|
||||
`live-gateway` ~958s, `live-models` ~1054s.
|
||||
- Installer/release lanes:
|
||||
`install-e2e` and package-update paths can vary widely with npm, provider,
|
||||
and package registry behavior. Budget tens of minutes; prefer GitHub targeted
|
||||
reruns over local repeats.
|
||||
|
||||
Default fallback lane timeout is 120 minutes. A timeout usually means debug the
|
||||
lane log/artifacts first, not “run the whole thing again.”
|
||||
|
||||
## Failure Workflow
|
||||
|
||||
1. Identify exact failing job, SHA, lane, and artifact path.
|
||||
2. Read `failures.json`, `summary.json`, and the failed lane log tail.
|
||||
3. Use `pnpm test:docker:rerun <run-id|failures.json>` to generate targeted
|
||||
GitHub rerun commands.
|
||||
4. If the lane has `rerunCommand`, use that only as a local starting point.
|
||||
5. For Docker release failures, dispatch targeted `docker_lanes=<failed-lane>`
|
||||
on GitHub before considering local Docker.
|
||||
6. Patch narrowly, then rerun the failed file/lane only.
|
||||
7. Broaden to `pnpm check:changed` or CI only after the isolated proof passes.
|
||||
|
||||
## When To Escalate
|
||||
|
||||
- Public SDK/plugin contract changes: run changed gate plus relevant extension
|
||||
validation.
|
||||
- Build output, lazy imports, package boundaries, or published surfaces:
|
||||
include `pnpm build`.
|
||||
- Workflow edits: run `pnpm check:workflows`.
|
||||
- Release branch or tag validation: use release docs and GitHub workflows; avoid
|
||||
local Docker unless Peter explicitly asks.
|
||||
@@ -1,4 +0,0 @@
|
||||
interface:
|
||||
display_name: "OpenClaw Testing"
|
||||
short_description: "Choose cheap, targeted OpenClaw validation"
|
||||
default_prompt: "Use $openclaw-testing to choose the cheapest safe test or CI verification path, inspect failures, and rerun only the relevant OpenClaw lane."
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: tag-duplicate-prs-issues
|
||||
description: Use gitcrawl to search duplicate OpenClaw PRs/issues, group related work in prtags, and sync duplicate state to GitHub.
|
||||
description: Search duplicate OpenClaw PRs/issues, group related work in prtags, and sync duplicate state to GitHub.
|
||||
---
|
||||
|
||||
# Tag Duplicate PRs and Issues
|
||||
@@ -12,25 +12,43 @@ It is not for reviewing the implementation quality of a PR.
|
||||
|
||||
## Required Setup
|
||||
|
||||
Do not write duplicate groups or annotations until this setup is complete.
|
||||
Read-only discovery can still proceed with `gitcrawl` and live `gh`.
|
||||
Do not start duplicate triage until this setup is complete.
|
||||
|
||||
### Companion Skills
|
||||
### Install the companion skills
|
||||
|
||||
Use `$gitcrawl` first for local candidate discovery.
|
||||
Use the `prtags` skill from the `prtags` repo at `skills/prtags/SKILL.md` when it is available.
|
||||
Install these skills first because they teach the agent how to use the two main CLIs correctly:
|
||||
|
||||
- `ghreplica` skill from the `ghreplica` repo at `skills/ghreplica/SKILL.md`
|
||||
- `prtags` skill from the `prtags` repo at `skills/prtags/SKILL.md`
|
||||
|
||||
This skill assumes those two skills are available and can be used during the same run.
|
||||
|
||||
### Install the CLIs
|
||||
|
||||
Install `prtags` from its latest GitHub release.
|
||||
Install `ghreplica` and `prtags` from their latest GitHub releases.
|
||||
Do not rely on an old local build unless the maintainer explicitly wants to test unreleased behavior.
|
||||
|
||||
`ghreplica` CLI install path:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/dutifuldev/ghreplica/main/scripts/install-ghr.sh | bash -s -- --bin-dir "$HOME/.local/bin"
|
||||
```
|
||||
|
||||
`prtags` CLI install path:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/dutifuldev/prtags/main/scripts/install-prtags.sh | bash -s -- --bin-dir "$HOME/.local/bin"
|
||||
```
|
||||
|
||||
Use the `pr-search-cli` project with `uvx`.
|
||||
The command itself is `pr-search`.
|
||||
Do not require a permanent install unless the maintainer explicitly wants one.
|
||||
|
||||
```bash
|
||||
uvx --from pr-search-cli pr-search status
|
||||
uvx --from pr-search-cli pr-search code similar 67144
|
||||
```
|
||||
|
||||
### Authenticate prtags
|
||||
|
||||
`prtags` should be logged in with the maintainer's own GitHub account through OAuth device flow.
|
||||
@@ -48,15 +66,20 @@ The expected outcome is that `prtags` stores the logged-in maintainer identity l
|
||||
Do not require an up-front preflight before starting the workflow.
|
||||
Proceed with the normal steps until you actually need a tool or account state.
|
||||
|
||||
As soon as you discover that `prtags` is missing or not logged in at the write step, stop immediately.
|
||||
Do not continue in a partial write mode after that point.
|
||||
As soon as you discover that a required CLI is missing or `prtags` is not logged in, stop immediately.
|
||||
Do not continue in a partial mode after that point.
|
||||
|
||||
If `prtags` is missing, ask the user to run:
|
||||
If `ghr` is missing, ask the user to run the `ghreplica` install command.
|
||||
|
||||
If `prtags` is missing, ask the user to run both CLI install commands:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/dutifuldev/ghreplica/main/scripts/install-ghr.sh | bash -s -- --bin-dir "$HOME/.local/bin"
|
||||
curl -fsSL https://raw.githubusercontent.com/dutifuldev/prtags/main/scripts/install-prtags.sh | bash -s -- --bin-dir "$HOME/.local/bin"
|
||||
```
|
||||
|
||||
If `uvx --from pr-search-cli pr-search ...` fails because `uvx` or the `pr-search` launcher is not available, ask the user to make that command work before continuing.
|
||||
|
||||
If `prtags auth status` shows that the user is not logged in, ask the user to run:
|
||||
|
||||
```bash
|
||||
@@ -67,19 +90,19 @@ Resume only after the missing tool or login state has been fixed.
|
||||
|
||||
## Read-Path Default
|
||||
|
||||
For candidate discovery in this workflow, use `gitcrawl` first.
|
||||
Treat it as the local history and clustering layer for related issues, duplicate attempts, and closed threads.
|
||||
For read-only GitHub operations in this workflow, use `ghr` as the default CLI.
|
||||
Treat it as a drop-in replacement for the `gh` read operations you would normally use for PRs, issues, comments, reviews, and duplicate-search evidence.
|
||||
|
||||
Use live `gh` or `gh api` for the target thread and for any candidate before making an actionable judgment.
|
||||
Use live GitHub when `gitcrawl` is missing or stale for a concrete reason, such as:
|
||||
Only fall back to `gh` when `ghr` is failing for a concrete reason, such as:
|
||||
|
||||
- the target or candidate is not present yet
|
||||
- the local data is clearly stale or incomplete for the decision you need to make
|
||||
- `gitcrawl` errors, times out, or lacks the needed neighbor/search data
|
||||
- the mirrored object is not present yet
|
||||
- the mirror data is clearly stale or incomplete for the decision you need to make
|
||||
- the `ghr` command errors, times out, or does not expose the specific read you need
|
||||
|
||||
When you fall back to live GitHub search, note that you did so and why.
|
||||
When you fall back to `gh`, note that you did so and why.
|
||||
|
||||
If a later `prtags` target-level write fails because its own mirror has not caught up, stop and report that the curation backend is missing the target object instead of forcing a fallback write.
|
||||
If `ghr` is missing a fresh PR or issue but `gh` can read it, you may use `gh` for the read-side judgment.
|
||||
If a later `prtags` target-level write fails because the same object is still missing from `ghreplica`, stop and report that the mirror has not caught up yet instead of forcing the write.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -95,12 +118,14 @@ For each target PR or issue:
|
||||
|
||||
Use the tools with these boundaries:
|
||||
|
||||
- `gitcrawl` is candidate generation and historical context
|
||||
- use it first for local title/body search, neighbors, clusters, and closed-thread discovery
|
||||
- treat every candidate as a lead until live GitHub confirms it
|
||||
- `gh` is live GitHub truth
|
||||
- use it for target state, body, comments, reviews, files, linked issues, and current open/closed/merged status
|
||||
- use `gh search` only when `gitcrawl` is stale, missing data, or cannot express the needed query
|
||||
- `ghreplica` is the raw evidence source
|
||||
- use `ghr` first for normal GitHub read operations in this workflow
|
||||
- use it for title/body/comment search, related PRs, overlapping files, overlapping ranges, and current PR or issue status
|
||||
- resort to `gh` only when `ghr` cannot provide the needed read cleanly
|
||||
- `pr-search-cli` is candidate generation and ranking
|
||||
- use it to suggest likely duplicate PRs or issue-cluster context
|
||||
- do not treat it as final truth
|
||||
- do not create or expand a duplicate group only because `pr-search-cli` put multiple PRs in the same issue or duplicate cluster
|
||||
- `prtags` is the maintainer curation layer
|
||||
- use it to create or reuse one duplicate group
|
||||
- use it to save the duplicate status, confidence, rationale, and group summary
|
||||
@@ -157,7 +182,7 @@ Examples:
|
||||
## Evidence Checklist
|
||||
|
||||
Before declaring a duplicate, gather evidence from at least two categories.
|
||||
`gitcrawl` neighbors, search hits, and cluster membership count as candidate generation, not as enough proof by themselves.
|
||||
Same-issue or same-cluster output from `pr-search-cli` counts only as candidate generation, not as one of the required proof categories by itself.
|
||||
|
||||
For PRs:
|
||||
|
||||
@@ -180,18 +205,21 @@ If you only have wording similarity, that is not enough.
|
||||
## Step 1: Read The Target
|
||||
|
||||
Start by reading the target itself.
|
||||
Use live GitHub for current target state.
|
||||
Use `ghr` first for this step even if you would normally reach for `gh`.
|
||||
|
||||
For a PR:
|
||||
|
||||
```bash
|
||||
gh pr view <number> --json number,title,state,mergedAt,body,closingIssuesReferences,files,comments,reviews,statusCheckRollup
|
||||
ghr pr view -R openclaw/openclaw <number> --comments
|
||||
ghr pr reviews -R openclaw/openclaw <number>
|
||||
ghr pr comments -R openclaw/openclaw <number>
|
||||
```
|
||||
|
||||
For an issue:
|
||||
|
||||
```bash
|
||||
gh issue view <number> --json number,title,state,body,comments,closedAt
|
||||
ghr issue view -R openclaw/openclaw <number> --comments
|
||||
ghr issue comments -R openclaw/openclaw <number>
|
||||
```
|
||||
|
||||
Record:
|
||||
@@ -204,56 +232,74 @@ Record:
|
||||
- whether it is open, closed, or merged
|
||||
- whether there is already a likely duplicate thread mentioned by humans
|
||||
|
||||
## Step 2: Search Broadly With Gitcrawl
|
||||
## Step 2: Search Broadly With ghreplica
|
||||
|
||||
Use `gitcrawl` first because it is the local OpenClaw history and clustering source.
|
||||
Do not switch to broad live GitHub search unless `gitcrawl` is missing data, stale, or failing.
|
||||
Use `ghreplica` first because it is the most direct evidence source.
|
||||
Do not switch to `gh` for ordinary reads unless `ghr` is missing data or failing.
|
||||
|
||||
Start with the target and nearby threads:
|
||||
### PR duplicate search
|
||||
|
||||
Run all of these when the target is a PR:
|
||||
|
||||
```bash
|
||||
gitcrawl threads openclaw/openclaw --numbers <issue-or-pr-number> --include-closed --json
|
||||
gitcrawl neighbors openclaw/openclaw --number <issue-or-pr-number> --limit 20 --json
|
||||
ghr search related-prs -R openclaw/openclaw <pr-number> --mode path_overlap --state all
|
||||
ghr search related-prs -R openclaw/openclaw <pr-number> --mode range_overlap --state all
|
||||
ghr search mentions -R openclaw/openclaw --query "<key phrase from title or body>" --mode fts --scope pull_requests --state all
|
||||
ghr search mentions -R openclaw/openclaw --query "<subsystem or error phrase>" --mode fts --scope issues --state all
|
||||
```
|
||||
|
||||
Then search key phrases and subsystem terms:
|
||||
Use `prs-by-paths` or `prs-by-ranges` when the likely duplicate surface is already known:
|
||||
|
||||
```bash
|
||||
gitcrawl search openclaw/openclaw --query "<key phrase from title or body>" --mode hybrid --limit 20 --json
|
||||
gitcrawl search openclaw/openclaw --query "<subsystem or error phrase>" --mode hybrid --limit 20 --json
|
||||
ghr search prs-by-paths -R openclaw/openclaw --path src/example.ts --state all
|
||||
ghr search prs-by-ranges -R openclaw/openclaw --path src/example.ts --start 20 --end 80 --state all
|
||||
```
|
||||
|
||||
Inspect likely clusters:
|
||||
### Issue duplicate search
|
||||
|
||||
`ghreplica` does not have a special issue-to-issue “related issues” command.
|
||||
For issues, search mirrored text and linked PR context instead.
|
||||
|
||||
Run targeted text searches:
|
||||
|
||||
```bash
|
||||
gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --body-chars 280 --json
|
||||
ghr search mentions -R openclaw/openclaw --query "<issue title phrase>" --mode fts --scope issues --state all
|
||||
ghr search mentions -R openclaw/openclaw --query "<error message or symptom>" --mode fts --scope issues --state all
|
||||
ghr search mentions -R openclaw/openclaw --query "<subsystem phrase>" --mode fts --scope pull_requests --state all
|
||||
```
|
||||
|
||||
For PRs, verify likely code overlap with live file data:
|
||||
Then inspect the candidate PRs or issues those searches uncover.
|
||||
|
||||
## Step 3: Use pr-search-cli As A Hint Layer
|
||||
|
||||
Use `pr-search-cli` after `ghreplica`.
|
||||
It is good at surfacing candidates quickly, but it is not the final decision-maker.
|
||||
Run it through the `pr-search` command.
|
||||
|
||||
For a PR:
|
||||
|
||||
```bash
|
||||
gh pr view <candidate-pr> --json number,title,state,mergedAt,files,body,comments,reviews
|
||||
uvx --from pr-search-cli pr-search -R openclaw/openclaw code similar <pr-number>
|
||||
uvx --from pr-search-cli pr-search -R openclaw/openclaw code clusters for-pr <pr-number>
|
||||
uvx --from pr-search-cli pr-search -R openclaw/openclaw issues for-pr <pr-number>
|
||||
uvx --from pr-search-cli pr-search -R openclaw/openclaw issues duplicate-prs
|
||||
```
|
||||
|
||||
For issues, verify likely duplicate issue state and comments live:
|
||||
Interpretation:
|
||||
|
||||
```bash
|
||||
gh issue view <candidate-issue> --json number,title,state,body,comments,closedAt
|
||||
```
|
||||
- `code similar` suggests PRs with similar change shape
|
||||
- `code clusters for-pr` shows the PR’s nearby code cluster
|
||||
- `issues for-pr` shows which issue clusters the PR appears to belong to
|
||||
- `issues duplicate-prs` is useful for spotting already-known duplicate PR patterns
|
||||
|
||||
## Step 3: Use Live GitHub Search For Gaps
|
||||
Treat every `pr-search-cli` result as a hint to investigate, not as enough evidence to create or widen a duplicate group.
|
||||
Multiple PRs can share the same issue or issue cluster while still taking meaningfully different fix paths.
|
||||
|
||||
Use targeted live GitHub search after `gitcrawl` when:
|
||||
For an issue:
|
||||
|
||||
- the target is too new for the local store
|
||||
- comments or reviews matter and the local store lacks them
|
||||
- the exact phrase did not appear in local results but the issue/PR is current enough that GitHub should know it
|
||||
|
||||
```bash
|
||||
gh search prs --repo openclaw/openclaw --match title,body --limit 50 -- "<key phrase>"
|
||||
gh search issues --repo openclaw/openclaw --match title,body --limit 50 -- "<key phrase>"
|
||||
gh search issues --repo openclaw/openclaw --match comments --limit 50 -- "<error or maintainer phrase>"
|
||||
```
|
||||
- use `ghreplica` first to find candidate PRs or issue wording
|
||||
- if the issue has linked PRs or a likely implementation PR, run `pr-search-cli` on those PRs
|
||||
- treat issue-cluster output as supporting context, not as enough by itself to call the issue a duplicate
|
||||
|
||||
## Step 4: Decide The Outcome
|
||||
|
||||
@@ -298,7 +344,7 @@ Reuse an existing group when:
|
||||
- it already contains clearly related members
|
||||
- adding the target would keep the group coherent
|
||||
|
||||
Do not widen an existing group just because `gitcrawl` placed several PRs or issues near each other.
|
||||
Do not widen an existing group just because `pr-search-cli` placed several PRs under the same issue or duplicate cluster.
|
||||
Confirm that the actual implementation path and maintainer intent still match before adding the new member.
|
||||
|
||||
Create a new group only when no existing group clearly fits.
|
||||
@@ -377,8 +423,8 @@ prtags annotation group set <group-id> \
|
||||
|
||||
When the evidence is incomplete, set `duplicate_status=candidate` and lower the confidence.
|
||||
|
||||
If a per-PR or per-issue annotation write fails because `prtags` cannot resolve the target, do not force a fallback write path.
|
||||
Keep the group state you were able to write, report that the curation backend is still missing the target object, and defer the target-level annotation until `prtags` catches up.
|
||||
If a per-PR or per-issue annotation write fails because `prtags` cannot resolve the target through `ghreplica`, do not force a fallback write path.
|
||||
Keep the group state you were able to write, report that the mirror is still missing the target object, and defer the target-level annotation until `ghreplica` catches up.
|
||||
|
||||
## Step 8: Let prtags Sync The Group Comment
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
interface:
|
||||
display_name: "Tag Duplicate PRs and Issues"
|
||||
short_description: "Find duplicate PRs and issues with gitcrawl, group them in prtags, and let prtags sync the GitHub comment"
|
||||
default_prompt: "Use $tag-duplicate-prs-issues to decide whether an OpenClaw PR or issue is a duplicate, gather candidates with gitcrawl, verify live state with GitHub, group related items in prtags, and save the duplicate judgment."
|
||||
short_description: "Find duplicate PRs and issues, group them in prtags, and let prtags sync the GitHub comment"
|
||||
default_prompt: "Use $tag-duplicate-prs-issues to decide whether an OpenClaw PR or issue is a duplicate, gather evidence with ghreplica and pr-search-cli, group related items in prtags, and save the duplicate judgment."
|
||||
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -9,8 +9,6 @@
|
||||
/.github/dependabot.yml @openclaw/secops
|
||||
/.github/codeql/ @openclaw/secops
|
||||
/.github/workflows/codeql.yml @openclaw/secops
|
||||
/.github/workflows/codeql-android-critical-security.yml @openclaw/secops
|
||||
/.github/workflows/codeql-critical-quality.yml @openclaw/secops
|
||||
/src/security/ @openclaw/secops
|
||||
/src/secrets/ @openclaw/secops
|
||||
/src/config/*secret*.ts @openclaw/secops
|
||||
|
||||
1
.github/actionlint.yaml
vendored
1
.github/actionlint.yaml
vendored
@@ -4,7 +4,6 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
# Blacksmith CI runners
|
||||
- blacksmith-4vcpu-ubuntu-2404
|
||||
- blacksmith-8vcpu-ubuntu-2404
|
||||
- blacksmith-8vcpu-windows-2025
|
||||
- blacksmith-16vcpu-ubuntu-2404
|
||||
|
||||
149
.github/actions/docker-e2e-plan/action.yml
vendored
149
.github/actions/docker-e2e-plan/action.yml
vendored
@@ -1,149 +0,0 @@
|
||||
name: Docker E2E plan and hydrate
|
||||
description: >
|
||||
Create a Docker E2E lane plan, expose GitHub outputs, and optionally hydrate
|
||||
the prebuilt package artifact plus shared Docker images needed by the plan.
|
||||
inputs:
|
||||
mode:
|
||||
description: prepare, chunk, or targeted.
|
||||
required: true
|
||||
chunk:
|
||||
description: Release-path chunk for mode=chunk.
|
||||
required: false
|
||||
default: ""
|
||||
lanes:
|
||||
description: Comma/space separated lane names for targeted or prepare mode.
|
||||
required: false
|
||||
default: ""
|
||||
include-openwebui:
|
||||
description: Whether Open WebUI is included when planning release/prepare coverage.
|
||||
required: false
|
||||
default: "true"
|
||||
include-release-path-suites:
|
||||
description: Whether prepare mode should plan all release-path suites.
|
||||
required: false
|
||||
default: "false"
|
||||
hydrate-artifacts:
|
||||
description: Whether to download/pull artifacts required by the plan.
|
||||
required: false
|
||||
default: "true"
|
||||
package-artifact-name:
|
||||
description: Workflow artifact name containing openclaw-current.tgz.
|
||||
required: false
|
||||
default: docker-e2e-package
|
||||
outputs:
|
||||
credentials:
|
||||
description: Comma-separated credential groups required by selected lanes.
|
||||
value: ${{ steps.plan.outputs.credentials }}
|
||||
needs_bare_image:
|
||||
description: "1 when selected lanes require the bare Docker E2E image."
|
||||
value: ${{ steps.plan.outputs.needs_bare_image }}
|
||||
needs_e2e_image:
|
||||
description: "1 when selected lanes require any Docker E2E image."
|
||||
value: ${{ steps.plan.outputs.needs_e2e_image }}
|
||||
needs_functional_image:
|
||||
description: "1 when selected lanes require the functional Docker E2E image."
|
||||
value: ${{ steps.plan.outputs.needs_functional_image }}
|
||||
needs_live_image:
|
||||
description: "1 when selected lanes require building the live Docker image."
|
||||
value: ${{ steps.plan.outputs.needs_live_image }}
|
||||
needs_package:
|
||||
description: "1 when selected lanes require the OpenClaw package tarball."
|
||||
value: ${{ steps.plan.outputs.needs_package }}
|
||||
plan_json:
|
||||
description: Path to the generated plan JSON.
|
||||
value: ${{ steps.plan.outputs.plan_json }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Plan Docker E2E lanes
|
||||
id: plan
|
||||
shell: bash
|
||||
env:
|
||||
MODE: ${{ inputs.mode }}
|
||||
CHUNK: ${{ inputs.chunk }}
|
||||
LANES: ${{ inputs.lanes }}
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include-openwebui }}
|
||||
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include-release-path-suites }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p .artifacts/docker-tests
|
||||
|
||||
case "$MODE" in
|
||||
prepare)
|
||||
plan_path=".artifacts/docker-tests/plan.json"
|
||||
if [[ "$INCLUDE_RELEASE_PATH_SUITES" == "true" ]]; then
|
||||
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
||||
export OPENCLAW_DOCKER_ALL_PLAN_RELEASE_ALL=1
|
||||
elif [[ -n "$LANES" ]]; then
|
||||
export OPENCLAW_DOCKER_ALL_LANES="$LANES"
|
||||
elif [[ "$INCLUDE_OPENWEBUI" == "true" ]]; then
|
||||
export OPENCLAW_DOCKER_ALL_LANES=openwebui
|
||||
fi
|
||||
;;
|
||||
chunk)
|
||||
if [[ -z "$CHUNK" ]]; then
|
||||
echo "chunk input is required for Docker E2E chunk planning." >&2
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_DOCKER_ALL_PROFILE=release-path
|
||||
export OPENCLAW_DOCKER_ALL_CHUNK="$CHUNK"
|
||||
plan_path=".artifacts/docker-tests/release-${CHUNK}-plan.json"
|
||||
;;
|
||||
targeted)
|
||||
if [[ -z "$LANES" ]]; then
|
||||
echo "lanes input is required for Docker E2E targeted planning." >&2
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_DOCKER_ALL_LANES="$LANES"
|
||||
plan_path=".artifacts/docker-tests/targeted-plan.json"
|
||||
;;
|
||||
*)
|
||||
echo "mode must be prepare, chunk, or targeted. Got: $MODE" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
export OPENCLAW_DOCKER_ALL_INCLUDE_OPENWEBUI="$INCLUDE_OPENWEBUI"
|
||||
node scripts/test-docker-all.mjs --plan-json > "$plan_path"
|
||||
node scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
|
||||
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download OpenClaw Docker E2E package
|
||||
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_package == '1'
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.package-artifact-name }}
|
||||
path: .artifacts/docker-e2e-package
|
||||
|
||||
- name: Pull shared bare Docker E2E image
|
||||
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_bare_image == '1'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${OPENCLAW_DOCKER_E2E_BARE_IMAGE}"
|
||||
|
||||
- name: Pull shared functional Docker E2E image
|
||||
if: inputs.hydrate-artifacts == 'true' && steps.plan.outputs.needs_functional_image == '1'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker pull "${OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE}"
|
||||
|
||||
- name: Validate Docker E2E credentials
|
||||
if: inputs.hydrate-artifacts == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
CREDENTIALS: ${{ steps.plan.outputs.credentials }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
credentials=",$CREDENTIALS,"
|
||||
if [[ "$credentials" == *",openai,"* ]]; then
|
||||
[[ -n "${OPENAI_API_KEY:-}" ]] || {
|
||||
echo "OPENAI_API_KEY is required for selected Docker E2E lanes." >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
if [[ "$credentials" == *",anthropic,"* && -z "${ANTHROPIC_API_TOKEN:-}" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
||||
echo "ANTHROPIC_API_TOKEN or ANTHROPIC_API_KEY is required for selected Docker E2E lanes." >&2
|
||||
exit 1
|
||||
fi
|
||||
2
.github/actions/setup-node-env/action.yml
vendored
2
.github/actions/setup-node-env/action.yml
vendored
@@ -90,11 +90,9 @@ runs:
|
||||
|
||||
install_args=(
|
||||
install
|
||||
--prefer-offline
|
||||
--ignore-scripts=false
|
||||
--config.engine-strict=false
|
||||
--config.enable-pre-post-scripts=true
|
||||
--config.side-effects-cache=true
|
||||
)
|
||||
if [ -n "$LOCKFILE_FLAG" ]; then
|
||||
install_args+=("$LOCKFILE_FLAG")
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
name: openclaw-codeql-actions-critical-security
|
||||
|
||||
paths:
|
||||
- .github/actions
|
||||
- .github/workflows
|
||||
|
||||
paths-ignore:
|
||||
- .github/workflows/stale.yml
|
||||
@@ -1,53 +0,0 @@
|
||||
name: openclaw-codeql-agent-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/acp/control-plane
|
||||
- src/agents/command
|
||||
- src/agents/cli-runner
|
||||
- src/agents/pi-embedded-runner
|
||||
- src/agents/tools
|
||||
- src/agents/*completion*.ts
|
||||
- src/agents/*transport*.ts
|
||||
- src/agents/model-*.ts
|
||||
- src/agents/openclaw-tools*.ts
|
||||
- src/agents/provider-*.ts
|
||||
- src/agents/session*.ts
|
||||
- src/agents/tool-call*.ts
|
||||
- src/auto-reply/reply/agent-runner*.ts
|
||||
- src/auto-reply/reply/commands*.ts
|
||||
- src/auto-reply/reply/directive-handling*.ts
|
||||
- src/auto-reply/reply/dispatch-*.ts
|
||||
- src/auto-reply/reply/get-reply-run*.ts
|
||||
- src/auto-reply/reply/provider-dispatcher*.ts
|
||||
- src/auto-reply/reply/queue*.ts
|
||||
- src/auto-reply/reply/reply-run-registry*.ts
|
||||
- src/auto-reply/reply/session*.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,30 +0,0 @@
|
||||
name: openclaw-codeql-android-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
# Android canvas intentionally runs trusted A2UI JavaScript; keep this profile focused on exploitable WebView edges.
|
||||
- exclude:
|
||||
id: java/android/websettings-javascript-enabled
|
||||
# Gateway TLS already pins verified certificate SHA-256 fingerprints. OkHttp CertificatePinner pins SPKI hashes,
|
||||
# so this query is noisy for OpenClaw's TOFU/local-gateway trust model and does not belong in the critical profile.
|
||||
- exclude:
|
||||
id: java/android/missing-certificate-pinning
|
||||
|
||||
paths:
|
||||
- apps/android/app/src/main
|
||||
|
||||
paths-ignore:
|
||||
- "**/.gradle"
|
||||
- "**/build"
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.*"
|
||||
- "**/*Test.kt"
|
||||
- "**/*Test.java"
|
||||
- "**/*Benchmark.kt"
|
||||
- apps/android/app/src/test
|
||||
- apps/android/benchmark
|
||||
@@ -1,33 +0,0 @@
|
||||
name: openclaw-codeql-channel-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/channels
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,50 +0,0 @@
|
||||
name: openclaw-codeql-channel-runtime-boundary-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- recommendation
|
||||
- warning
|
||||
|
||||
paths:
|
||||
- src/channels
|
||||
- src/config/channel-*.ts
|
||||
- src/config/types.channel*.ts
|
||||
- src/gateway/server-channel*.ts
|
||||
- src/gateway/server-methods/channels.ts
|
||||
- src/gateway/protocol/schema/channels.ts
|
||||
- src/infra/channel-*.ts
|
||||
- src/infra/exec-approval-channel-runtime.ts
|
||||
- src/infra/outbound/channel-*.ts
|
||||
- src/plugin-sdk/channel-*.ts
|
||||
- src/plugins/channel-*.ts
|
||||
- src/plugins/bundled-channel-*.ts
|
||||
- src/plugins/runtime/*channel*.ts
|
||||
- src/secrets/channel-*.ts
|
||||
- src/secrets/runtime-config-collectors-channels.ts
|
||||
- src/security/audit-channel*.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,33 +0,0 @@
|
||||
name: openclaw-codeql-config-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/config
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,53 +0,0 @@
|
||||
name: openclaw-codeql-core-auth-secrets-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/agents/*auth*.ts
|
||||
- src/agents/**/*auth*.ts
|
||||
- src/agents/auth-health*.ts
|
||||
- src/agents/auth-profiles
|
||||
- src/agents/bash-tools.exec-host-shared.ts
|
||||
- src/agents/sandbox
|
||||
- src/agents/sandbox.ts
|
||||
- src/agents/sandbox-*.ts
|
||||
- src/cron/service/jobs.ts
|
||||
- src/cron/stagger.ts
|
||||
- src/gateway/*auth*.ts
|
||||
- src/gateway/**/*auth*.ts
|
||||
- src/gateway/*secret*.ts
|
||||
- src/gateway/**/*secret*.ts
|
||||
- src/gateway/protocol/**/*secret*.ts
|
||||
- src/gateway/resolve-configured-secret-input-string*.ts
|
||||
- src/gateway/security-path*.ts
|
||||
- src/gateway/server-methods/secrets*.ts
|
||||
- src/infra/secret-file*.ts
|
||||
- src/secrets
|
||||
- src/security
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,57 +0,0 @@
|
||||
name: openclaw-codeql-core-auth-secrets-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- recommendation
|
||||
- warning
|
||||
|
||||
paths:
|
||||
- src/agents/*auth*.ts
|
||||
- src/agents/**/*auth*.ts
|
||||
- src/agents/auth-health*.ts
|
||||
- src/agents/auth-profiles
|
||||
- src/agents/bash-tools.exec-host-shared.ts
|
||||
- src/agents/sandbox
|
||||
- src/agents/sandbox.ts
|
||||
- src/agents/sandbox-*.ts
|
||||
- src/config/*secret*.ts
|
||||
- src/config/**/*secret*.ts
|
||||
- src/cron/service/jobs.ts
|
||||
- src/cron/stagger.ts
|
||||
- src/gateway/*auth*.ts
|
||||
- src/gateway/**/*auth*.ts
|
||||
- src/gateway/*secret*.ts
|
||||
- src/gateway/**/*secret*.ts
|
||||
- src/gateway/protocol/**/*secret*.ts
|
||||
- src/gateway/resolve-configured-secret-input-string*.ts
|
||||
- src/gateway/security-path*.ts
|
||||
- src/gateway/server-methods/secrets*.ts
|
||||
- src/infra/secret-file*.ts
|
||||
- src/secrets
|
||||
- src/security
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,34 +0,0 @@
|
||||
name: openclaw-codeql-gateway-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/gateway/protocol
|
||||
- src/gateway/server-methods
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
27
.github/codeql/codeql-javascript-typescript-core.yml
vendored
Normal file
27
.github/codeql/codeql-javascript-typescript-core.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: openclaw-codeql-javascript-typescript-core
|
||||
|
||||
paths:
|
||||
- src/agents
|
||||
- src/plugin-sdk
|
||||
- src/infra
|
||||
- src/gateway
|
||||
- src/commands
|
||||
- src/auto-reply
|
||||
- src/plugins
|
||||
- src/cli
|
||||
- src/config
|
||||
- src/channels
|
||||
|
||||
paths-ignore:
|
||||
- apps
|
||||
- dist
|
||||
- docs
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
18
.github/codeql/codeql-javascript-typescript-extensions.yml
vendored
Normal file
18
.github/codeql/codeql-javascript-typescript-extensions.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: openclaw-codeql-javascript-typescript-extensions
|
||||
|
||||
paths:
|
||||
- extensions
|
||||
|
||||
paths-ignore:
|
||||
- apps
|
||||
- dist
|
||||
- docs
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
30
.github/codeql/codeql-javascript-typescript-tail.yml
vendored
Normal file
30
.github/codeql/codeql-javascript-typescript-tail.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: openclaw-codeql-javascript-typescript-tail
|
||||
|
||||
paths:
|
||||
- src
|
||||
- ui/src
|
||||
- skills
|
||||
|
||||
paths-ignore:
|
||||
- apps
|
||||
- dist
|
||||
- docs
|
||||
- src/agents/**
|
||||
- src/plugin-sdk/**
|
||||
- src/infra/**
|
||||
- src/gateway/**
|
||||
- src/commands/**
|
||||
- src/auto-reply/**
|
||||
- src/plugins/**
|
||||
- src/cli/**
|
||||
- src/config/**
|
||||
- src/channels/**
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
@@ -1,17 +0,0 @@
|
||||
name: openclaw-codeql-macos-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
paths:
|
||||
- apps/macos/Sources
|
||||
|
||||
paths-ignore:
|
||||
- "**/.build"
|
||||
- "**/.build/**"
|
||||
- "**/DerivedData"
|
||||
- "**/DerivedData/**"
|
||||
- "**/*.generated.swift"
|
||||
- "**/*Tests.swift"
|
||||
@@ -1,35 +0,0 @@
|
||||
name: openclaw-codeql-mcp-process-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/mcp
|
||||
- src/process
|
||||
- src/infra/outbound
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,58 +0,0 @@
|
||||
name: openclaw-codeql-mcp-process-tool-boundary-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- recommendation
|
||||
- warning
|
||||
|
||||
paths:
|
||||
- src/mcp
|
||||
- src/process
|
||||
- src/infra/outbound
|
||||
- src/agents/bash-tools.exec*.ts
|
||||
- src/agents/bash-tools.process*.ts
|
||||
- src/agents/exec-*.ts
|
||||
- src/agents/execution-contract.ts
|
||||
- src/agents/openclaw-plugin-tools.ts
|
||||
- src/agents/openclaw-tools.runtime.ts
|
||||
- src/agents/openclaw-tools.registration.ts
|
||||
- src/agents/pi-tool-definition-adapter.ts
|
||||
- src/agents/pi-tools.abort.ts
|
||||
- src/agents/pi-tools.before-tool-call*.ts
|
||||
- src/agents/pi-tools.host-edit.ts
|
||||
- src/agents/pi-tools-parameter-schema.ts
|
||||
- src/agents/pi-embedded-runner/effective-tool-policy.ts
|
||||
- src/agents/pi-embedded-runner/tool-name-allowlist.ts
|
||||
- src/agents/pi-embedded-runner/tool-schema-runtime.ts
|
||||
- src/agents/tools/gateway-tool.ts
|
||||
- src/agents/tools/message-tool.ts
|
||||
- src/agents/tools/sessions-send-tool.ts
|
||||
- src/agents/tools/sessions-spawn-tool.ts
|
||||
- src/agents/tools/subagents-tool.ts
|
||||
- src/agents/tools/tool-runtime.helpers.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,41 +0,0 @@
|
||||
name: openclaw-codeql-memory-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- packages/memory-host-sdk/src
|
||||
- src/memory
|
||||
- src/memory-host-sdk
|
||||
- src/plugin-sdk/memory-*.ts
|
||||
- src/plugin-sdk/memory-core-host-*.ts
|
||||
- src/plugins/memory-*.ts
|
||||
- src/gateway/server-startup-memory.ts
|
||||
- src/commands/doctor-memory-search.ts
|
||||
- src/commands/doctor-cron-dreaming-payload-migration.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,43 +0,0 @@
|
||||
name: openclaw-codeql-network-ssrf-boundary-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- recommendation
|
||||
- warning
|
||||
|
||||
paths:
|
||||
- src/infra/net
|
||||
- src/shared/net
|
||||
- src/agents/tools/web-fetch.ts
|
||||
- src/agents/tools/web-guarded-fetch.ts
|
||||
- src/agents/tools/web-shared.ts
|
||||
- src/plugin-sdk/ssrf-policy.ts
|
||||
- src/web-fetch
|
||||
- src/web/provider-runtime-shared.ts
|
||||
- packages/memory-host-sdk/src/host/ssrf-policy.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,76 +0,0 @@
|
||||
name: openclaw-codeql-plugin-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/plugins/activation-planner.ts
|
||||
- src/plugins/api-builder.ts
|
||||
- src/plugins/bundled-compat.ts
|
||||
- src/plugins/bundled-dir.ts
|
||||
- src/plugins/bundled-plugin-metadata.ts
|
||||
- src/plugins/bundled-public-surface-runtime-root.ts
|
||||
- src/plugins/bundled-runtime-deps.ts
|
||||
- src/plugins/bundled-runtime-root.ts
|
||||
- src/plugins/captured-registration.ts
|
||||
- src/plugins/config-activation-shared.ts
|
||||
- src/plugins/config-contracts.ts
|
||||
- src/plugins/config-normalization-shared.ts
|
||||
- src/plugins/config-policy.ts
|
||||
- src/plugins/config-schema.ts
|
||||
- src/plugins/config-state.ts
|
||||
- src/plugins/discovery.ts
|
||||
- src/plugins/effective-plugin-ids.ts
|
||||
- src/plugins/externalized-bundled-plugins.ts
|
||||
- src/plugins/installed-plugin-index*.ts
|
||||
- src/plugins/loader*.ts
|
||||
- src/plugins/manifest*.ts
|
||||
- src/plugins/module-export.ts
|
||||
- src/plugins/package-entrypoints.ts
|
||||
- src/plugins/plugin-registry*.ts
|
||||
- src/plugins/provider-contract-public-artifacts.ts
|
||||
- src/plugins/provider-public-artifacts.ts
|
||||
- src/plugins/public-surface*.ts
|
||||
- src/plugins/registry.ts
|
||||
- src/plugins/registry-types.ts
|
||||
- src/plugins/runtime
|
||||
- src/plugins/runtime-state.ts
|
||||
- src/plugins/runtime.ts
|
||||
- src/plugins/sdk-alias.ts
|
||||
- src/plugins/source-loader.ts
|
||||
- src/plugins/types.ts
|
||||
- src/plugins/validation-diagnostics.ts
|
||||
- src/plugins/web-provider-public-artifacts*.ts
|
||||
- src/plugin-sdk/*entry*.ts
|
||||
- src/plugin-sdk/*facade*.ts
|
||||
- src/plugin-sdk/api-baseline.ts
|
||||
- src/plugin-sdk/config-schema.ts
|
||||
- src/plugin-sdk/config-types.ts
|
||||
- src/plugin-sdk/core.ts
|
||||
- src/plugin-sdk/extension-shared.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,36 +0,0 @@
|
||||
name: openclaw-codeql-plugin-sdk-package-contract-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- packages/plugin-sdk/src
|
||||
- packages/plugin-package-contract/src
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.spec.ts"
|
||||
- "**/*.spec.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,89 +0,0 @@
|
||||
name: openclaw-codeql-plugin-trust-boundary-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
- exclude:
|
||||
problem.severity:
|
||||
- recommendation
|
||||
- warning
|
||||
|
||||
paths:
|
||||
- src/cli/plugin-install-config-policy.ts
|
||||
- src/cli/plugin-registry-loader.ts
|
||||
- src/cli/plugins-command-helpers.ts
|
||||
- src/cli/plugins-install-command.ts
|
||||
- src/cli/plugins-install-record-commit.ts
|
||||
- src/plugins/activation-planner.ts
|
||||
- src/plugins/bundle-manifest.ts
|
||||
- src/plugins/bundled-compat.ts
|
||||
- src/plugins/bundled-dir.ts
|
||||
- src/plugins/bundled-plugin-metadata.ts
|
||||
- src/plugins/bundled-plugin-scan.ts
|
||||
- src/plugins/bundled-runtime-deps*.ts
|
||||
- src/plugins/bundled-runtime-root.ts
|
||||
- src/plugins/cli-registry-loader.ts
|
||||
- src/plugins/config-activation-shared.ts
|
||||
- src/plugins/config-contracts.ts
|
||||
- src/plugins/config-policy.ts
|
||||
- src/plugins/config-schema.ts
|
||||
- src/plugins/dependency-denylist.ts
|
||||
- src/plugins/discovery.ts
|
||||
- src/plugins/effective-plugin-ids.ts
|
||||
- src/plugins/externalized-bundled-plugins.ts
|
||||
- src/plugins/install.runtime.ts
|
||||
- src/plugins/install-source-info.ts
|
||||
- src/plugins/installed-plugin-index*.ts
|
||||
- src/plugins/loader*.ts
|
||||
- src/plugins/manifest*.ts
|
||||
- src/plugins/marketplace.ts
|
||||
- src/plugins/module-export.ts
|
||||
- src/plugins/package-entrypoints.ts
|
||||
- src/plugins/plugin-config-trust.ts
|
||||
- src/plugins/plugin-origin.types.ts
|
||||
- src/plugins/plugin-registry*.ts
|
||||
- src/plugins/public-surface*.ts
|
||||
- src/plugins/registry*.ts
|
||||
- src/plugins/runtime
|
||||
- src/plugins/runtime-state.ts
|
||||
- src/plugins/runtime.ts
|
||||
- src/plugins/source-loader.ts
|
||||
- src/plugins/update.ts
|
||||
- src/plugins/validation-diagnostics.ts
|
||||
- src/plugin-sdk/*entry*.ts
|
||||
- src/plugin-sdk/*facade*.ts
|
||||
- src/plugin-sdk/api-baseline.ts
|
||||
- src/plugin-sdk/config-schema.ts
|
||||
- src/plugin-sdk/config-types.ts
|
||||
- src/plugin-sdk/core.ts
|
||||
- src/plugin-sdk/extension-shared.ts
|
||||
- packages/plugin-package-contract/src
|
||||
- packages/plugin-sdk/src/plugin-entry.ts
|
||||
- packages/plugin-sdk/src/plugin-runtime.ts
|
||||
- packages/plugin-sdk/src/runtime-env.ts
|
||||
- packages/plugin-sdk/src/security-runtime.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.spec.ts"
|
||||
- "**/*.spec.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,48 +0,0 @@
|
||||
name: openclaw-codeql-session-diagnostics-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/auto-reply/reply/queue
|
||||
- src/auto-reply/reply/post-compaction-context.ts
|
||||
- src/auto-reply/reply/startup-context.ts
|
||||
- src/infra/diagnostic-*.ts
|
||||
- src/infra/diagnostics-timeline.ts
|
||||
- src/infra/session-delivery-queue*.ts
|
||||
- src/infra/outbound/base-session-key.ts
|
||||
- src/infra/outbound/delivery-queue*.ts
|
||||
- src/infra/outbound/outbound-session.ts
|
||||
- src/infra/outbound/session-binding*.ts
|
||||
- src/infra/outbound/session-context.ts
|
||||
- src/infra/outbound/targets-session.ts
|
||||
- src/logging/diagnostic*.ts
|
||||
- src/commands/doctor-session-*.ts
|
||||
- src/commands/session-store-targets.ts
|
||||
- src/commands/sessions*.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,36 +0,0 @@
|
||||
name: openclaw-codeql-ui-control-plane-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- ui/src/main.ts
|
||||
- ui/src/local-storage.ts
|
||||
- ui/src/ui
|
||||
- src/tasks/task-registry-control*.ts
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
@@ -1,39 +0,0 @@
|
||||
name: openclaw-codeql-web-media-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/web-fetch
|
||||
- src/web-search
|
||||
- src/web/provider-runtime-shared.ts
|
||||
- src/media
|
||||
- src/media-understanding
|
||||
- src/image-generation
|
||||
- src/media-generation
|
||||
|
||||
paths-ignore:
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.generated.ts"
|
||||
- "**/*.bundle.js"
|
||||
- "**/*-runtime.js"
|
||||
- "**/*.test.ts"
|
||||
- "**/*.test.tsx"
|
||||
- "**/*.e2e.test.ts"
|
||||
- "**/*.e2e.test.tsx"
|
||||
- "**/*test-support*"
|
||||
- "**/*test-helper*"
|
||||
- "**/*mock*"
|
||||
- "**/*fixture*"
|
||||
- "**/*bench*"
|
||||
16
.github/images/live-media-runner/Dockerfile
vendored
16
.github/images/live-media-runner/Dockerfile
vendored
@@ -1,16 +0,0 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
ffmpeg \
|
||||
git \
|
||||
openssh-client \
|
||||
unzip \
|
||||
xz-utils \
|
||||
zstd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
33
.github/labeler.yml
vendored
33
.github/labeler.yml
vendored
@@ -35,17 +35,6 @@
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/google-meet/**"
|
||||
- "docs/plugins/google-meet.md"
|
||||
"plugin: migrate-hermes":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/migrate-hermes/**"
|
||||
- "docs/cli/migrate.md"
|
||||
"plugin: migrate-claude":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/migrate-claude/**"
|
||||
- "docs/cli/migrate.md"
|
||||
- "docs/install/migrating-claude.md"
|
||||
"plugin: bonjour":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -112,11 +101,6 @@
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/slack/**"
|
||||
- "docs/channels/slack.md"
|
||||
"channel: synology-chat":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/synology-chat/**"
|
||||
- "docs/channels/synology-chat.md"
|
||||
"channel: telegram":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -238,11 +222,8 @@
|
||||
"security":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- ".github/workflows/opengrep-*.yml"
|
||||
- ".semgrepignore"
|
||||
- "docs/cli/security.md"
|
||||
- "docs/gateway/security.md"
|
||||
- "security/**"
|
||||
|
||||
"extensions: copilot-proxy":
|
||||
- changed-files:
|
||||
@@ -252,10 +233,6 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/diagnostics-otel/**"
|
||||
"extensions: diagnostics-prometheus":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/diagnostics-prometheus/**"
|
||||
"extensions: llm-task":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -308,20 +285,10 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/byteplus/**"
|
||||
"extensions: cerebras":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/cerebras/**"
|
||||
- "docs/providers/cerebras.md"
|
||||
"extensions: deepseek":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/deepseek/**"
|
||||
"extensions: deepinfra":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/deepinfra/**"
|
||||
- "docs/providers/deepinfra.md"
|
||||
"extensions: tencent":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
224
.github/workflows/ci-build-artifacts-testbox.yml
vendored
224
.github/workflows/ci-build-artifacts-testbox.yml
vendored
@@ -1,224 +0,0 @@
|
||||
name: Blacksmith Build Artifacts Testbox
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
testbox_id:
|
||||
type: string
|
||||
description: "Testbox session ID"
|
||||
required: true
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
build-artifacts:
|
||||
permissions:
|
||||
contents: read
|
||||
name: "build-artifacts"
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 35
|
||||
steps:
|
||||
- name: Begin Testbox
|
||||
uses: useblacksmith/begin-testbox@d0e04585c26905fdd92c94a09c159544c7ee1b67
|
||||
with:
|
||||
testbox_id: ${{ inputs.testbox_id }}
|
||||
|
||||
- name: Checkout
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
workdir="$GITHUB_WORKSPACE"
|
||||
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
||||
|
||||
reset_checkout_dir() {
|
||||
mkdir -p "$workdir"
|
||||
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||
}
|
||||
|
||||
checkout_attempt() {
|
||||
local attempt="$1"
|
||||
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
|
||||
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
||||
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
||||
echo "checkout attempt ${attempt}/5 succeeded"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if checkout_attempt "$attempt"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "checkout attempt ${attempt}/5 failed"
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
|
||||
echo "checkout failed after 5 attempts" >&2
|
||||
exit 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Resolve release dist cache seeds
|
||||
id: dist-cache-seeds
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cache_prefix="${RUNNER_OS}-dist-build-"
|
||||
declare -A seen=()
|
||||
|
||||
resolve_tag_sha() {
|
||||
local tag="$1"
|
||||
local direct=""
|
||||
local peeled=""
|
||||
|
||||
while read -r sha ref; do
|
||||
if [[ "$ref" == "refs/tags/${tag}^{}" ]]; then
|
||||
peeled="$sha"
|
||||
elif [[ "$ref" == "refs/tags/${tag}" ]]; then
|
||||
direct="$sha"
|
||||
fi
|
||||
done < <(git ls-remote --tags origin "refs/tags/${tag}" "refs/tags/${tag}^{}")
|
||||
|
||||
printf '%s\n' "${peeled:-$direct}"
|
||||
}
|
||||
|
||||
{
|
||||
echo "restore-keys<<EOF"
|
||||
for dist_tag in beta latest; do
|
||||
version="$(npm view "openclaw@${dist_tag}" version 2>/dev/null || true)"
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "Could not resolve npm dist-tag ${dist_tag}; skipping cache seed." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
sha="$(resolve_tag_sha "v${version}")"
|
||||
if [[ -z "$sha" ]]; then
|
||||
echo "Could not resolve git tag v${version}; skipping cache seed." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
key="${cache_prefix}${sha}"
|
||||
if [[ -z "${seen[$key]+x}" ]]; then
|
||||
echo "$key"
|
||||
seen[$key]=1
|
||||
fi
|
||||
done
|
||||
echo "${cache_prefix}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore dist build cache
|
||||
id: dist-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: |
|
||||
.artifacts/build-all-cache/
|
||||
dist/
|
||||
dist-runtime/
|
||||
key: ${{ runner.os }}-dist-build-${{ github.sha }}
|
||||
restore-keys: ${{ steps.dist-cache-seeds.outputs.restore-keys }}
|
||||
|
||||
- name: Build dist on cache miss
|
||||
if: steps.dist-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm build:ci-artifacts
|
||||
|
||||
- name: Build Control UI on cache miss
|
||||
if: steps.dist-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm ui:build
|
||||
|
||||
- name: Verify build artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
test -d dist
|
||||
test -d dist-runtime
|
||||
if [[ ! -f dist/index.js && ! -f dist/index.mjs ]]; then
|
||||
echo "Missing dist/index.js or dist/index.mjs" >&2
|
||||
exit 1
|
||||
fi
|
||||
test -f dist/build-info.json
|
||||
test -f dist/control-ui/index.html
|
||||
|
||||
- name: Save dist build cache
|
||||
if: steps.dist-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: |
|
||||
.artifacts/build-all-cache/
|
||||
dist/
|
||||
dist-runtime/
|
||||
key: ${{ runner.os }}-dist-build-${{ github.sha }}
|
||||
|
||||
- name: Prepare Testbox shell
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git fetch --no-tags --depth=50 origin "+refs/heads/main:refs/remotes/origin/main"
|
||||
|
||||
node_bin="$(dirname "$(node -p 'process.execPath')")"
|
||||
pnpm_bin="$(command -v pnpm)"
|
||||
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 ln -sf "$pnpm_bin" /usr/local/bin/pnpm
|
||||
|
||||
- name: Hydrate Testbox provider env helper
|
||||
shell: bash
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
run: bash scripts/ci-hydrate-testbox-env.sh
|
||||
|
||||
- name: Run Testbox
|
||||
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
|
||||
if: always()
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
31
.github/workflows/ci-check-testbox.yml
vendored
31
.github/workflows/ci-check-testbox.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Begin Testbox
|
||||
uses: useblacksmith/begin-testbox@d0e04585c26905fdd92c94a09c159544c7ee1b67
|
||||
uses: useblacksmith/begin-testbox@v2
|
||||
with:
|
||||
testbox_id: ${{ inputs.testbox_id }}
|
||||
- name: Checkout
|
||||
@@ -93,35 +93,8 @@ jobs:
|
||||
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
|
||||
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
|
||||
sudo ln -sf "$pnpm_bin" /usr/local/bin/pnpm
|
||||
|
||||
- name: Hydrate Testbox provider env helper
|
||||
shell: bash
|
||||
env:
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
run: bash scripts/ci-hydrate-testbox-env.sh
|
||||
|
||||
- name: Run Testbox
|
||||
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
|
||||
uses: useblacksmith/run-testbox@v2
|
||||
if: always()
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
509
.github/workflows/ci.yml
vendored
509
.github/workflows/ci.yml
vendored
@@ -1,18 +1,6 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_ref:
|
||||
description: Optional branch, tag, or full commit SHA to validate instead of the workflow ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
include_android:
|
||||
description: Run Android lanes for this manual CI dispatch.
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
@@ -25,8 +13,8 @@ permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.event_name == 'workflow_dispatch' && format('{0}-manual-v1-{1}', github.workflow, github.run_id) || (github.event_name == 'pull_request' && format('{0}-v7-{1}', github.workflow, github.event.pull_request.number) || (github.repository == 'openclaw/openclaw' && format('{0}-v7-{1}', github.workflow, github.ref) || format('{0}-v7-{1}-{2}', github.workflow, github.ref, github.sha))) }}
|
||||
cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
group: ${{ github.event_name == 'pull_request' && format('{0}-v7-{1}', github.workflow, github.event.pull_request.number) || (github.repository == 'openclaw/openclaw' && format('{0}-v7-{1}', github.workflow, github.ref) || format('{0}-v7-{1}-{2}', github.workflow, github.ref, github.sha)) }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -41,7 +29,6 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
checkout_revision: ${{ steps.checkout_ref.outputs.sha }}
|
||||
docs_only: ${{ steps.manifest.outputs.docs_only }}
|
||||
docs_changed: ${{ steps.manifest.outputs.docs_changed }}
|
||||
run_node: ${{ steps.manifest.outputs.run_node }}
|
||||
@@ -50,19 +37,22 @@ jobs:
|
||||
run_skills_python: ${{ steps.manifest.outputs.run_skills_python }}
|
||||
run_skills_python_job: ${{ steps.manifest.outputs.run_skills_python_job }}
|
||||
run_windows: ${{ steps.manifest.outputs.run_windows }}
|
||||
has_changed_extensions: ${{ steps.manifest.outputs.has_changed_extensions }}
|
||||
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
|
||||
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
|
||||
run_checks_fast_core: ${{ steps.manifest.outputs.run_checks_fast_core }}
|
||||
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
|
||||
checks_fast_core_matrix: ${{ steps.manifest.outputs.checks_fast_core_matrix }}
|
||||
run_plugin_contracts_shards: ${{ steps.manifest.outputs.run_plugin_contracts_shards }}
|
||||
plugin_contracts_matrix: ${{ steps.manifest.outputs.plugin_contracts_matrix }}
|
||||
channel_contracts_matrix: ${{ steps.manifest.outputs.channel_contracts_matrix }}
|
||||
checks_node_extensions_matrix: ${{ steps.manifest.outputs.checks_node_extensions_matrix }}
|
||||
run_checks: ${{ steps.manifest.outputs.run_checks }}
|
||||
checks_matrix: ${{ steps.manifest.outputs.checks_matrix }}
|
||||
run_checks_node_core_nondist: ${{ steps.manifest.outputs.run_checks_node_core_nondist }}
|
||||
checks_node_core_nondist_matrix: ${{ steps.manifest.outputs.checks_node_core_nondist_matrix }}
|
||||
run_checks_node_core_dist: ${{ steps.manifest.outputs.run_checks_node_core_dist }}
|
||||
checks_node_core_dist_matrix: ${{ steps.manifest.outputs.checks_node_core_dist_matrix }}
|
||||
run_extension_fast: ${{ steps.manifest.outputs.run_extension_fast }}
|
||||
extension_fast_matrix: ${{ steps.manifest.outputs.extension_fast_matrix }}
|
||||
run_check: ${{ steps.manifest.outputs.run_check }}
|
||||
run_check_additional: ${{ steps.manifest.outputs.run_check_additional }}
|
||||
run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }}
|
||||
@@ -79,18 +69,12 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Resolve checkout SHA
|
||||
id: checkout_ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Ensure preflight base commit
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: ./.github/actions/ensure-base-commit
|
||||
with:
|
||||
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
||||
@@ -98,12 +82,11 @@ jobs:
|
||||
|
||||
- name: Detect docs-only changes
|
||||
id: docs_scope
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: ./.github/actions/detect-docs-changes
|
||||
|
||||
- name: Detect changed scopes
|
||||
id: changed_scope
|
||||
if: github.event_name != 'workflow_dispatch' && steps.docs_scope.outputs.docs_only != 'true'
|
||||
if: steps.docs_scope.outputs.docs_only != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -116,21 +99,45 @@ jobs:
|
||||
|
||||
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
|
||||
|
||||
- name: Detect changed extensions
|
||||
id: changed_extensions
|
||||
if: steps.docs_scope.outputs.docs_only != 'true' && steps.changed_scope.outputs.run_node == 'true'
|
||||
env:
|
||||
BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
||||
BASE_REF: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
|
||||
run: |
|
||||
node --input-type=module <<'EOF'
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { listChangedExtensionIds } from "./scripts/lib/changed-extensions.mjs";
|
||||
|
||||
const extensionIds = listChangedExtensionIds({
|
||||
base: process.env.BASE_SHA,
|
||||
head: "HEAD",
|
||||
fallbackBaseRef: process.env.BASE_REF,
|
||||
unavailableBaseBehavior: "all",
|
||||
});
|
||||
const matrix = JSON.stringify({ include: extensionIds.map((extension) => ({ extension })) });
|
||||
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `has_changed_extensions=${extensionIds.length > 0}\n`, "utf8");
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `changed_extensions_matrix=${matrix}\n`, "utf8");
|
||||
EOF
|
||||
|
||||
- name: Build CI manifest
|
||||
id: manifest
|
||||
env:
|
||||
OPENCLAW_CI_DOCS_ONLY: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.docs_scope.outputs.docs_only }}
|
||||
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_ANDROID: ${{ github.event_name == 'workflow_dispatch' && 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' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_plugin_contracts || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ github.event_name == 'workflow_dispatch' && 'false' || steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }}
|
||||
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_skills_python || 'false' }}
|
||||
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
|
||||
OPENCLAW_CI_CHECKOUT_REVISION: ${{ steps.checkout_ref.outputs.sha }}
|
||||
OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }}
|
||||
OPENCLAW_CI_DOCS_CHANGED: ${{ steps.docs_scope.outputs.docs_changed }}
|
||||
OPENCLAW_CI_RUN_NODE: ${{ steps.changed_scope.outputs.run_node || 'false' }}
|
||||
OPENCLAW_CI_RUN_MACOS: ${{ steps.changed_scope.outputs.run_macos || 'false' }}
|
||||
OPENCLAW_CI_RUN_ANDROID: ${{ steps.changed_scope.outputs.run_android || 'false' }}
|
||||
OPENCLAW_CI_RUN_WINDOWS: ${{ steps.changed_scope.outputs.run_windows || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_ONLY: ${{ steps.changed_scope.outputs.run_node_fast_only || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS: ${{ steps.changed_scope.outputs.run_node_fast_plugin_contracts || 'false' }}
|
||||
OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING: ${{ steps.changed_scope.outputs.run_node_fast_ci_routing || 'false' }}
|
||||
OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }}
|
||||
OPENCLAW_CI_RUN_CONTROL_UI_I18N: ${{ steps.changed_scope.outputs.run_control_ui_i18n || 'false' }}
|
||||
OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }}
|
||||
OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: ${{ steps.changed_extensions.outputs.changed_extensions_matrix || '{"include":[]}' }}
|
||||
OPENCLAW_CI_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
node --input-type=module <<'EOF'
|
||||
@@ -141,6 +148,10 @@ jobs:
|
||||
import {
|
||||
createChannelContractTestShards,
|
||||
} from "./scripts/lib/channel-contract-test-plan.mjs";
|
||||
import {
|
||||
createExtensionTestShards,
|
||||
DEFAULT_EXTENSION_TEST_SHARD_COUNT,
|
||||
} from "./scripts/lib/extension-test-plan.mjs";
|
||||
|
||||
const parseBoolean = (value, fallback = false) => {
|
||||
if (value === undefined) return fallback;
|
||||
@@ -150,26 +161,18 @@ jobs:
|
||||
return fallback;
|
||||
};
|
||||
|
||||
const { createPluginContractTestShards } = await import(
|
||||
"./scripts/lib/plugin-contract-test-plan.mjs"
|
||||
).catch((error) => {
|
||||
if (error?.code !== "ERR_MODULE_NOT_FOUND") {
|
||||
throw error;
|
||||
const parseJson = (value, fallback) => {
|
||||
try {
|
||||
return value ? JSON.parse(value) : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
return {
|
||||
createPluginContractTestShards: () => [
|
||||
{
|
||||
checkName: "checks-fast-contracts-plugins-legacy",
|
||||
includePatterns: ["src/plugins/contracts/**/*.test.ts"],
|
||||
runtime: "node",
|
||||
task: "contracts-plugins",
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const createMatrix = (include) => ({ include });
|
||||
const outputPath = process.env.GITHUB_OUTPUT;
|
||||
const eventName = process.env.GITHUB_EVENT_NAME ?? "pull_request";
|
||||
const isPush = eventName === "push";
|
||||
const isCanonicalRepository = process.env.OPENCLAW_CI_REPOSITORY === "openclaw/openclaw";
|
||||
const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY);
|
||||
const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED);
|
||||
@@ -181,7 +184,7 @@ jobs:
|
||||
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_PLUGIN_CONTRACTS);
|
||||
const runNodeFastCiRouting =
|
||||
runNode && parseBoolean(process.env.OPENCLAW_CI_RUN_NODE_FAST_CI_ROUTING);
|
||||
const runPluginContractShards = runNodeFull || runNodeFastPluginContracts;
|
||||
const runChecksFastCore = runNodeFull || runNodeFastPluginContracts || runNodeFastCiRouting;
|
||||
const runMacos =
|
||||
parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly && isCanonicalRepository;
|
||||
const runAndroid =
|
||||
@@ -194,13 +197,49 @@ jobs:
|
||||
const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly;
|
||||
const runControlUiI18n =
|
||||
parseBoolean(process.env.OPENCLAW_CI_RUN_CONTROL_UI_I18N) && !docsOnly;
|
||||
const hasChangedExtensions =
|
||||
parseBoolean(process.env.OPENCLAW_CI_HAS_CHANGED_EXTENSIONS) && !docsOnly;
|
||||
const changedExtensionsMatrix = hasChangedExtensions
|
||||
? parseJson(process.env.OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX, { include: [] })
|
||||
: { include: [] };
|
||||
const extensionTestShardCount = isCanonicalRepository
|
||||
? DEFAULT_EXTENSION_TEST_SHARD_COUNT
|
||||
: Math.max(DEFAULT_EXTENSION_TEST_SHARD_COUNT, 36);
|
||||
const extensionShardMatrix = createMatrix(
|
||||
runNodeFull
|
||||
? createExtensionTestShards({
|
||||
shardCount: extensionTestShardCount,
|
||||
}).map((shard) => ({
|
||||
check_name: shard.checkName,
|
||||
extensions_csv: shard.extensionIds.join(","),
|
||||
runner: isCanonicalRepository && [0, 3, 4].includes(shard.index)
|
||||
? "blacksmith-8vcpu-ubuntu-2404"
|
||||
: isCanonicalRepository
|
||||
? "blacksmith-4vcpu-ubuntu-2404"
|
||||
: "ubuntu-24.04",
|
||||
shard_index: shard.index + 1,
|
||||
task: "extensions-batch",
|
||||
}))
|
||||
: [],
|
||||
);
|
||||
const checksFastCoreTasks = [];
|
||||
if (runNodeFull) {
|
||||
checksFastCoreTasks.push(
|
||||
{ check_name: "checks-fast-bundled", runtime: "node", task: "bundled" },
|
||||
{
|
||||
check_name: "checks-fast-contracts-plugins",
|
||||
runtime: "node",
|
||||
task: "contracts-plugins",
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (runNodeFastCiRouting) {
|
||||
if (runNodeFastPluginContracts) {
|
||||
checksFastCoreTasks.push({
|
||||
check_name: "checks-fast-contracts-plugins",
|
||||
runtime: "node",
|
||||
task: runNodeFastCiRouting ? "contracts-plugins-ci-routing" : "contracts-plugins",
|
||||
});
|
||||
} else if (runNodeFastCiRouting) {
|
||||
checksFastCoreTasks.push({
|
||||
check_name: "checks-fast-ci-routing",
|
||||
runtime: "node",
|
||||
@@ -210,9 +249,7 @@ jobs:
|
||||
}
|
||||
|
||||
const nodeTestShards = runNodeFull
|
||||
? createNodeTestShards({
|
||||
includeReleaseOnlyPluginShards: false,
|
||||
}).map((shard) => ({
|
||||
? createNodeTestShards().map((shard) => ({
|
||||
check_name: shard.checkName,
|
||||
runtime: "node",
|
||||
task: "test-shard",
|
||||
@@ -234,17 +271,16 @@ jobs:
|
||||
run_android: runAndroid,
|
||||
run_skills_python: runSkillsPython,
|
||||
run_windows: runWindows,
|
||||
has_changed_extensions: hasChangedExtensions,
|
||||
changed_extensions_matrix: changedExtensionsMatrix,
|
||||
run_build_artifacts: runNodeFull,
|
||||
run_checks_fast_core: checksFastCoreTasks.length > 0,
|
||||
run_checks_fast_core: runChecksFastCore,
|
||||
run_checks_fast: runNodeFull,
|
||||
checks_fast_core_matrix: createMatrix(checksFastCoreTasks),
|
||||
run_plugin_contracts_shards: runPluginContractShards,
|
||||
plugin_contracts_matrix: createMatrix(
|
||||
runPluginContractShards ? createPluginContractTestShards() : [],
|
||||
),
|
||||
channel_contracts_matrix: createMatrix(
|
||||
runNodeFull ? createChannelContractTestShards() : [],
|
||||
),
|
||||
checks_node_extensions_matrix: extensionShardMatrix,
|
||||
run_checks: runNodeFull,
|
||||
checks_matrix: createMatrix(
|
||||
runNodeFull
|
||||
@@ -257,6 +293,15 @@ jobs:
|
||||
checks_node_core_nondist_matrix: createMatrix(nodeTestNonDistShards),
|
||||
run_checks_node_core_dist: nodeTestDistShards.length > 0,
|
||||
checks_node_core_dist_matrix: createMatrix(nodeTestDistShards),
|
||||
run_extension_fast: hasChangedExtensions && !isPush,
|
||||
extension_fast_matrix: createMatrix(
|
||||
hasChangedExtensions && !isPush
|
||||
? (changedExtensionsMatrix.include ?? []).map((entry) => ({
|
||||
check_name: `extension-fast-${entry.extension}`,
|
||||
extension: entry.extension,
|
||||
}))
|
||||
: [],
|
||||
),
|
||||
run_check: runNodeFull,
|
||||
run_check_additional: runNodeFull,
|
||||
run_build_smoke: runNodeFull,
|
||||
@@ -309,14 +354,12 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Ensure security base commit
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: ./.github/actions/ensure-base-commit
|
||||
with:
|
||||
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
||||
@@ -400,7 +443,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
@@ -463,7 +505,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -535,7 +577,7 @@ jobs:
|
||||
path: |
|
||||
dist/
|
||||
dist-runtime/
|
||||
key: ${{ runner.os }}-dist-build-${{ needs.preflight.outputs.checkout_revision }}
|
||||
key: ${{ runner.os }}-dist-build-${{ github.sha }}
|
||||
|
||||
- name: Pack built runtime artifacts
|
||||
run: tar --posix -cf dist-runtime-build.tar.zst --use-compress-program zstdmt dist dist-runtime
|
||||
@@ -664,7 +706,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -743,112 +785,6 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
checks-fast-plugin-contracts-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
name: ${{ matrix.checkName }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_plugin_contracts_shards == 'true'
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.plugin_contracts_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
workdir="$GITHUB_WORKSPACE"
|
||||
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
||||
|
||||
reset_checkout_dir() {
|
||||
mkdir -p "$workdir"
|
||||
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||
}
|
||||
|
||||
checkout_attempt() {
|
||||
local attempt="$1"
|
||||
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
|
||||
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
||||
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
||||
echo "checkout attempt ${attempt}/5 succeeded"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if checkout_attempt "$attempt"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "checkout attempt ${attempt}/5 failed"
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
|
||||
echo "checkout failed after 5 attempts" >&2
|
||||
exit 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Run plugin contract shard
|
||||
env:
|
||||
OPENCLAW_CONTRACT_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
include_file="$RUNNER_TEMP/plugin-contract-include.json"
|
||||
INCLUDE_FILE="$include_file" node --input-type=module <<'EOF'
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const includePatterns = JSON.parse(process.env.OPENCLAW_CONTRACT_INCLUDE_PATTERNS_JSON ?? "[]");
|
||||
if (!Array.isArray(includePatterns) || includePatterns.length === 0) {
|
||||
console.error("Missing plugin contract include patterns");
|
||||
process.exit(1);
|
||||
}
|
||||
writeFileSync(process.env.INCLUDE_FILE, JSON.stringify(includePatterns), "utf8");
|
||||
EOF
|
||||
OPENCLAW_VITEST_INCLUDE_FILE="$include_file" pnpm test:contracts:plugins
|
||||
|
||||
checks-fast-plugin-contracts:
|
||||
permissions:
|
||||
contents: read
|
||||
name: checks-fast-contracts-plugins
|
||||
needs: [preflight, checks-fast-plugin-contracts-shard]
|
||||
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_plugin_contracts_shards == 'true' }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify plugin contract shards
|
||||
env:
|
||||
SHARD_RESULT: ${{ needs.checks-fast-plugin-contracts-shard.result }}
|
||||
run: |
|
||||
if [ "$SHARD_RESULT" = "cancelled" ]; then
|
||||
echo "Plugin contract shards were cancelled, usually because a newer commit superseded this run." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$SHARD_RESULT" != "success" ]; then
|
||||
echo "Plugin contract shards failed: $SHARD_RESULT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checks-fast-channel-contracts-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -865,7 +801,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -968,7 +904,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1020,6 +956,97 @@ jobs:
|
||||
- name: Run protocol check
|
||||
run: pnpm protocol:check
|
||||
|
||||
checks-node-extensions-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_checks_fast == 'true'
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_extensions_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
workdir="$GITHUB_WORKSPACE"
|
||||
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
||||
|
||||
reset_checkout_dir() {
|
||||
mkdir -p "$workdir"
|
||||
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||
}
|
||||
|
||||
checkout_attempt() {
|
||||
local attempt="$1"
|
||||
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
|
||||
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
||||
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
||||
echo "checkout attempt ${attempt}/5 succeeded"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if checkout_attempt "$attempt"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "checkout attempt ${attempt}/5 failed"
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
|
||||
echo "checkout failed after 5 attempts" >&2
|
||||
exit 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Run extension shard
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
OPENCLAW_EXTENSION_BATCH_PARALLEL: 2
|
||||
OPENCLAW_VITEST_MAX_WORKERS: 1
|
||||
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
|
||||
run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH"
|
||||
|
||||
checks-node-extensions:
|
||||
permissions:
|
||||
contents: read
|
||||
name: checks-node-extensions
|
||||
needs: [preflight, checks-node-extensions-shard]
|
||||
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_fast == 'true' }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify extension shards
|
||||
env:
|
||||
SHARD_RESULT: ${{ needs.checks-node-extensions-shard.result }}
|
||||
run: |
|
||||
if [ "$SHARD_RESULT" != "success" ]; then
|
||||
echo "Extension shard checks failed: $SHARD_RESULT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
checks:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -1057,7 +1084,7 @@ jobs:
|
||||
contents: read
|
||||
name: checks-node-compat-node22
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'workflow_dispatch'
|
||||
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'push'
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
@@ -1065,7 +1092,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1145,7 +1172,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1296,6 +1323,84 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
extension-fast:
|
||||
permissions:
|
||||
contents: read
|
||||
name: "extension-fast"
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_extension_fast == 'true'
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.extension_fast_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
workdir="$GITHUB_WORKSPACE"
|
||||
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
|
||||
|
||||
reset_checkout_dir() {
|
||||
mkdir -p "$workdir"
|
||||
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
||||
}
|
||||
|
||||
checkout_attempt() {
|
||||
local attempt="$1"
|
||||
|
||||
reset_checkout_dir
|
||||
git init "$workdir" >/dev/null
|
||||
git config --global --add safe.directory "$workdir"
|
||||
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
|
||||
git -C "$workdir" config gc.auto 0
|
||||
|
||||
timeout --signal=TERM 30s git -C "$workdir" \
|
||||
-c protocol.version=2 \
|
||||
-c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
|
||||
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
|
||||
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
|
||||
|
||||
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
|
||||
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
|
||||
echo "checkout attempt ${attempt}/5 succeeded"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if checkout_attempt "$attempt"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "checkout attempt ${attempt}/5 failed"
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
|
||||
echo "checkout failed after 5 attempts" >&2
|
||||
exit 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Run changed extension tests
|
||||
env:
|
||||
OPENCLAW_CHANGED_EXTENSION: ${{ matrix.extension }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "$OPENCLAW_CHANGED_EXTENSION" = "telegram" ]; then
|
||||
export OPENCLAW_VITEST_MAX_WORKERS=1
|
||||
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--max-old-space-size=6144"
|
||||
pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION" -- --pool=forks
|
||||
exit 0
|
||||
fi
|
||||
pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION"
|
||||
|
||||
# Types, lint, and format check shards.
|
||||
check-shard:
|
||||
permissions:
|
||||
@@ -1318,9 +1423,6 @@ jobs:
|
||||
- check_name: check-lint
|
||||
task: lint
|
||||
runner: blacksmith-16vcpu-ubuntu-2404
|
||||
- check_name: check-dependencies
|
||||
task: dependencies
|
||||
runner: ubuntu-24.04
|
||||
- check_name: check-policy-guards
|
||||
task: policy-guards
|
||||
runner: ubuntu-24.04
|
||||
@@ -1335,7 +1437,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1396,7 +1498,6 @@ jobs:
|
||||
pnpm check:no-conflict-markers
|
||||
pnpm tool-display:check
|
||||
pnpm check:host-env-policy:swift
|
||||
pnpm dup:check:coverage
|
||||
;;
|
||||
prod-types)
|
||||
pnpm tsgo:prod
|
||||
@@ -1404,14 +1505,6 @@ jobs:
|
||||
lint)
|
||||
pnpm lint --threads=8
|
||||
;;
|
||||
dependencies)
|
||||
if pnpm run --silent 2>/dev/null | grep -q '^ deadcode:dependencies$'; then
|
||||
pnpm deadcode:dependencies
|
||||
pnpm deadcode:unused-files
|
||||
else
|
||||
pnpm deadcode:ci
|
||||
fi
|
||||
;;
|
||||
policy-guards)
|
||||
pnpm lint:webhook:no-low-level-body-read
|
||||
pnpm lint:auth:no-pairing-store-group
|
||||
@@ -1476,7 +1569,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1674,7 +1767,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -1737,7 +1830,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -1780,7 +1872,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -1885,7 +1976,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -1926,7 +2016,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
@@ -2027,7 +2116,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
CHECKOUT_REPO: ${{ github.repository }}
|
||||
CHECKOUT_SHA: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
CHECKOUT_SHA: ${{ github.sha }}
|
||||
CHECKOUT_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -2083,14 +2172,6 @@ jobs:
|
||||
apps/android/**/gradle-wrapper.properties
|
||||
apps/android/gradle/libs.versions.toml
|
||||
|
||||
- name: Cache Android SDK
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.android-sdk
|
||||
key: ${{ runner.os }}-android-sdk-v1-cmdline-12266719-platform-36-build-tools-36.0.0
|
||||
restore-keys: |
|
||||
${{ runner.os }}-android-sdk-v1-
|
||||
|
||||
- name: Setup Android SDK cmdline-tools
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -2099,13 +2180,11 @@ jobs:
|
||||
ARCHIVE="commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip"
|
||||
URL="https://dl.google.com/android/repository/${ARCHIVE}"
|
||||
|
||||
if [ ! -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then
|
||||
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"
|
||||
curl -fsSL "$URL" -o "/tmp/${ARCHIVE}"
|
||||
rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest"
|
||||
unzip -q "/tmp/${ARCHIVE}" -d "$ANDROID_SDK_ROOT/cmdline-tools"
|
||||
mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest"
|
||||
fi
|
||||
mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools"
|
||||
curl -fsSL "$URL" -o "/tmp/${ARCHIVE}"
|
||||
rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest"
|
||||
unzip -q "/tmp/${ARCHIVE}" -d "$ANDROID_SDK_ROOT/cmdline-tools"
|
||||
mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest"
|
||||
|
||||
echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
|
||||
echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
|
||||
|
||||
103
.github/workflows/clawsweeper-dispatch.yml
vendored
103
.github/workflows/clawsweeper-dispatch.yml
vendored
@@ -1,103 +0,0 @@
|
||||
name: ClawSweeper Dispatch
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened, edited, labeled, unlabeled]
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned external dispatch; no checkout or untrusted PR code execution
|
||||
types: [opened, reopened, synchronize, ready_for_review, edited, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: clawsweeper-dispatch-${{ github.repository }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event.action == 'edited' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review' }}
|
||||
|
||||
jobs:
|
||||
dispatch:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !(endsWith(github.actor, '[bot]') && (github.event.action == 'labeled' || github.event.action == 'unlabeled')) }}
|
||||
env:
|
||||
HAS_CLAWSWEEPER_APP_PRIVATE_KEY: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY != '' }}
|
||||
CLAWSWEEPER_APP_CLIENT_ID: Iv23liOECG0slfuhz093
|
||||
SUPERSEDES_IN_PROGRESS: ${{ (github.event.action == 'edited' || github.event.action == 'synchronize' || github.event.action == 'ready_for_review') && 'true' || 'false' }}
|
||||
steps:
|
||||
- name: Debounce bursty metadata events
|
||||
if: ${{ github.event.action == 'labeled' || github.event.action == 'unlabeled' }}
|
||||
run: sleep 20
|
||||
|
||||
- name: Create ClawSweeper dispatch token
|
||||
id: token
|
||||
if: ${{ env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true' }}
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
with:
|
||||
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.CLAWSWEEPER_APP_PRIVATE_KEY }}
|
||||
owner: openclaw
|
||||
repositories: clawsweeper
|
||||
permission-contents: write
|
||||
|
||||
- name: Dispatch exact ClawSweeper review
|
||||
if: ${{ github.event_name != 'push' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||
TARGET_REPO: ${{ github.repository }}
|
||||
ITEM_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
|
||||
ITEM_KIND: ${{ github.event_name == 'pull_request_target' && 'pull_request' || 'issue' }}
|
||||
SOURCE_EVENT: ${{ github.event_name }}
|
||||
SOURCE_ACTION: ${{ github.event.action }}
|
||||
run: |
|
||||
if [ -z "$GH_TOKEN" ]; then
|
||||
echo "::notice::Skipping ClawSweeper dispatch because no ClawSweeper app token is configured. Not falling back to a maintainer token."
|
||||
exit 0
|
||||
fi
|
||||
payload="$(jq -nc \
|
||||
--arg target_repo "$TARGET_REPO" \
|
||||
--argjson item_number "$ITEM_NUMBER" \
|
||||
--arg item_kind "$ITEM_KIND" \
|
||||
--arg source_event "$SOURCE_EVENT" \
|
||||
--arg source_action "$SOURCE_ACTION" \
|
||||
--argjson supersedes_in_progress "$SUPERSEDES_IN_PROGRESS" \
|
||||
'{event_type:"clawsweeper_item",client_payload:{target_repo:$target_repo,item_number:$item_number,item_kind:$item_kind,source_event:$source_event,source_action:$source_action,supersedes_in_progress:$supersedes_in_progress}}')"
|
||||
if gh api repos/openclaw/clawsweeper/dispatches \
|
||||
--method POST \
|
||||
--input - <<< "$payload"; then
|
||||
echo "Dispatched ClawSweeper review."
|
||||
else
|
||||
echo "::warning::Skipping ClawSweeper dispatch because the configured credential could not dispatch to openclaw/clawsweeper."
|
||||
fi
|
||||
|
||||
- name: Dispatch ClawSweeper commit review
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.event.deleted != true }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.token.outputs.token }}
|
||||
TARGET_REPO: ${{ github.repository }}
|
||||
BEFORE_SHA: ${{ github.event.before }}
|
||||
AFTER_SHA: ${{ github.sha }}
|
||||
SOURCE_REF: ${{ github.ref }}
|
||||
CREATE_CHECKS: ${{ vars.CLAWSWEEPER_COMMIT_REVIEW_CREATE_CHECKS || 'false' }}
|
||||
run: |
|
||||
if [ -z "$GH_TOKEN" ]; then
|
||||
echo "::notice::Skipping ClawSweeper commit dispatch because no ClawSweeper app token is configured. Not falling back to a maintainer token."
|
||||
exit 0
|
||||
fi
|
||||
case "$CREATE_CHECKS" in
|
||||
true|TRUE|1|yes|YES|on|ON) create_checks=true ;;
|
||||
*) create_checks=false ;;
|
||||
esac
|
||||
payload="$(jq -nc \
|
||||
--arg target_repo "$TARGET_REPO" \
|
||||
--arg before_sha "$BEFORE_SHA" \
|
||||
--arg after_sha "$AFTER_SHA" \
|
||||
--arg ref "$SOURCE_REF" \
|
||||
--argjson create_checks "$create_checks" \
|
||||
'{event_type:"clawsweeper_commit_review",client_payload:{target_repo:$target_repo,before_sha:$before_sha,after_sha:$after_sha,ref:$ref,enabled:true,create_checks:$create_checks}}')"
|
||||
if gh api repos/openclaw/clawsweeper/dispatches \
|
||||
--method POST \
|
||||
--input - <<< "$payload"; then
|
||||
echo "Dispatched ClawSweeper commit review."
|
||||
else
|
||||
echo "::warning::Skipping ClawSweeper commit dispatch because the configured credential could not dispatch to openclaw/clawsweeper."
|
||||
fi
|
||||
@@ -1,51 +0,0 @@
|
||||
name: CodeQL Android Critical Security
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 7 * * *"
|
||||
|
||||
concurrency:
|
||||
group: codeql-android-critical-security-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
android:
|
||||
name: Critical Security (android)
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: java-kotlin
|
||||
build-mode: manual
|
||||
config-file: ./.github/codeql/codeql-android-critical-security.yml
|
||||
|
||||
- name: Build Android for CodeQL
|
||||
working-directory: apps/android
|
||||
run: ./gradlew --no-daemon :app:assemblePlayDebug
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-security/android"
|
||||
293
.github/workflows/codeql-critical-quality.yml
vendored
293
.github/workflows/codeql-critical-quality.yml
vendored
@@ -1,293 +0,0 @@
|
||||
name: CodeQL Critical Quality
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
profile:
|
||||
description: CodeQL quality profile to run
|
||||
required: false
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- plugin-sdk-package-contract
|
||||
- session-diagnostics-boundary
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
|
||||
concurrency:
|
||||
group: codeql-critical-quality-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
core-auth-secrets:
|
||||
name: Critical Quality (core-auth-secrets)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-core-auth-secrets-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/core-auth-secrets"
|
||||
|
||||
config-boundary:
|
||||
name: Critical Quality (config-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-config-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/config-boundary"
|
||||
|
||||
gateway-runtime-boundary:
|
||||
name: Critical Quality (gateway-runtime-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-gateway-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/gateway-runtime-boundary"
|
||||
|
||||
channel-runtime-boundary:
|
||||
name: Critical Quality (channel-runtime-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-channel-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/channel-runtime-boundary"
|
||||
|
||||
agent-runtime-boundary:
|
||||
name: Critical Quality (agent-runtime-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-agent-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/agent-runtime-boundary"
|
||||
|
||||
mcp-process-runtime-boundary:
|
||||
name: Critical Quality (mcp-process-runtime-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-mcp-process-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/mcp-process-runtime-boundary"
|
||||
|
||||
memory-runtime-boundary:
|
||||
name: Critical Quality (memory-runtime-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-memory-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/memory-runtime-boundary"
|
||||
|
||||
session-diagnostics-boundary:
|
||||
name: Critical Quality (session-diagnostics-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'session-diagnostics-boundary' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-session-diagnostics-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/session-diagnostics-boundary"
|
||||
|
||||
ui-control-plane:
|
||||
name: Critical Quality (ui-control-plane)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-ui-control-plane-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/ui-control-plane"
|
||||
|
||||
web-media-runtime-boundary:
|
||||
name: Critical Quality (web-media-runtime-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-web-media-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/web-media-runtime-boundary"
|
||||
|
||||
plugin-boundary:
|
||||
name: Critical Quality (plugin-boundary)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-plugin-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/plugin-boundary"
|
||||
|
||||
plugin-sdk-package-contract:
|
||||
name: Critical Quality (plugin-sdk-package-contract)
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'plugin-sdk-package-contract' }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
config-file: ./.github/codeql/codeql-plugin-sdk-package-contract-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/plugin-sdk-package-contract"
|
||||
@@ -1,89 +0,0 @@
|
||||
name: CodeQL macOS Critical Security
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 8 * * 1"
|
||||
|
||||
concurrency:
|
||||
group: codeql-macos-critical-security-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
name: Critical Security (macOS)
|
||||
runs-on: blacksmith-6vcpu-macos-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Select Xcode
|
||||
run: |
|
||||
sudo xcode-select -s /Applications/Xcode_26.1.app
|
||||
xcodebuild -version
|
||||
swift --version
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
languages: swift
|
||||
build-mode: manual
|
||||
config-file: ./.github/codeql/codeql-macos-critical-security.yml
|
||||
|
||||
- name: Build macOS for CodeQL
|
||||
run: swift build --package-path apps/macos --product OpenClaw
|
||||
|
||||
- name: Analyze
|
||||
id: analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
output: sarif-results
|
||||
upload: failure-only
|
||||
category: "/codeql-critical-security/macos"
|
||||
|
||||
- name: Remove dependency build results
|
||||
env:
|
||||
SARIF_OUTPUT: sarif-results
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
if [ ! -d "$SARIF_OUTPUT" ]; then
|
||||
echo "SARIF output directory not found: $SARIF_OUTPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p sarif-results-filtered
|
||||
|
||||
files=("$SARIF_OUTPUT"/*.sarif)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No SARIF files found in $SARIF_OUTPUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
jq '
|
||||
def in_dependency_build:
|
||||
((.locations // []) | length > 0)
|
||||
and all(.locations[]; (.physicalLocation.artifactLocation.uri? // "") | test("^apps/macos/\\.build/"));
|
||||
|
||||
.runs |= map(.results = ((.results // []) | map(select(in_dependency_build | not))))
|
||||
' "$file" > "sarif-results-filtered/$(basename "$file")"
|
||||
done
|
||||
|
||||
- name: Upload filtered SARIF
|
||||
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
sarif_file: sarif-results-filtered
|
||||
category: "/codeql-critical-security/macos"
|
||||
184
.github/workflows/codeql.yml
vendored
184
.github/workflows/codeql.yml
vendored
@@ -2,21 +2,12 @@ name: CodeQL
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
profile:
|
||||
description: CodeQL security profile to run
|
||||
required: false
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- security
|
||||
schedule:
|
||||
- cron: "0 6 * * *"
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.sha }}
|
||||
cancel-in-progress: false
|
||||
group: codeql-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -27,58 +18,153 @@ permissions:
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
critical-security:
|
||||
name: Critical Security (${{ matrix.category }})
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'security' }}
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.job_name }})
|
||||
runs-on: ${{ matrix.runs_on }}
|
||||
timeout-minutes: ${{ matrix.timeout_minutes }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
category: core-auth-secrets
|
||||
runs_on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout_minutes: 25
|
||||
config_file: ./.github/codeql/codeql-core-auth-secrets-critical-security.yml
|
||||
- language: javascript-typescript
|
||||
category: channel-runtime-boundary
|
||||
runs_on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout_minutes: 25
|
||||
config_file: ./.github/codeql/codeql-channel-runtime-boundary-critical-security.yml
|
||||
- language: javascript-typescript
|
||||
category: network-ssrf-boundary
|
||||
runs_on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout_minutes: 25
|
||||
config_file: ./.github/codeql/codeql-network-ssrf-boundary-critical-security.yml
|
||||
- language: javascript-typescript
|
||||
category: mcp-process-tool-boundary
|
||||
runs_on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout_minutes: 25
|
||||
config_file: ./.github/codeql/codeql-mcp-process-tool-boundary-critical-security.yml
|
||||
- language: javascript-typescript
|
||||
category: plugin-trust-boundary
|
||||
runs_on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout_minutes: 25
|
||||
config_file: ./.github/codeql/codeql-plugin-trust-boundary-critical-security.yml
|
||||
- language: actions
|
||||
category: actions
|
||||
runs_on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout_minutes: 10
|
||||
config_file: ./.github/codeql/codeql-actions-critical-security.yml
|
||||
- job_name: javascript-typescript-core
|
||||
language: javascript-typescript
|
||||
runs_on: blacksmith-32vcpu-ubuntu-2404
|
||||
needs_node: true
|
||||
needs_python: false
|
||||
needs_java: false
|
||||
needs_swift_tools: false
|
||||
needs_manual_build: false
|
||||
needs_autobuild: false
|
||||
analyze_category: javascript-typescript-core
|
||||
config_file: ./.github/codeql/codeql-javascript-typescript-core.yml
|
||||
- job_name: javascript-typescript-extensions
|
||||
language: javascript-typescript
|
||||
runs_on: blacksmith-32vcpu-ubuntu-2404
|
||||
needs_node: true
|
||||
needs_python: false
|
||||
needs_java: false
|
||||
needs_swift_tools: false
|
||||
needs_manual_build: false
|
||||
needs_autobuild: false
|
||||
analyze_category: javascript-typescript-extensions
|
||||
config_file: ./.github/codeql/codeql-javascript-typescript-extensions.yml
|
||||
- job_name: javascript-typescript-tail
|
||||
language: javascript-typescript
|
||||
runs_on: blacksmith-32vcpu-ubuntu-2404
|
||||
needs_node: true
|
||||
needs_python: false
|
||||
needs_java: false
|
||||
needs_swift_tools: false
|
||||
needs_manual_build: false
|
||||
needs_autobuild: false
|
||||
analyze_category: javascript-typescript-tail
|
||||
config_file: ./.github/codeql/codeql-javascript-typescript-tail.yml
|
||||
- job_name: actions
|
||||
language: actions
|
||||
runs_on: blacksmith-16vcpu-ubuntu-2404
|
||||
needs_node: false
|
||||
needs_python: false
|
||||
needs_java: false
|
||||
needs_swift_tools: false
|
||||
needs_manual_build: false
|
||||
needs_autobuild: false
|
||||
analyze_category: actions
|
||||
config_file: ""
|
||||
- job_name: python
|
||||
language: python
|
||||
runs_on: blacksmith-16vcpu-ubuntu-2404
|
||||
needs_node: false
|
||||
needs_python: true
|
||||
needs_java: false
|
||||
needs_swift_tools: false
|
||||
needs_manual_build: false
|
||||
needs_autobuild: false
|
||||
analyze_category: python
|
||||
config_file: ""
|
||||
- job_name: java-kotlin
|
||||
language: java-kotlin
|
||||
runs_on: blacksmith-16vcpu-ubuntu-2404
|
||||
needs_node: false
|
||||
needs_python: false
|
||||
needs_java: true
|
||||
needs_swift_tools: false
|
||||
needs_manual_build: true
|
||||
needs_autobuild: false
|
||||
analyze_category: java-kotlin
|
||||
config_file: ""
|
||||
- job_name: swift
|
||||
language: swift
|
||||
runs_on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-12vcpu-macos-latest' || 'macos-latest' }}
|
||||
needs_node: false
|
||||
needs_python: false
|
||||
needs_java: false
|
||||
needs_swift_tools: true
|
||||
needs_manual_build: true
|
||||
needs_autobuild: false
|
||||
analyze_category: swift
|
||||
config_file: ""
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
if: matrix.needs_node
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Setup Python
|
||||
if: matrix.needs_python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Setup Java
|
||||
if: matrix.needs_java
|
||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
|
||||
- name: Setup Swift build tools
|
||||
if: matrix.needs_swift_tools
|
||||
run: |
|
||||
sudo xcode-select -s /Applications/Xcode_26.1.app
|
||||
xcodebuild -version
|
||||
brew install xcodegen swiftlint swiftformat
|
||||
swift --version
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
uses: github/codeql-action/init@b25d0ebf40e5b63ee81e1bd6e5d2a12b7c2aeb61 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ${{ matrix.config_file }}
|
||||
queries: security-and-quality
|
||||
config-file: ${{ matrix.config_file || '' }}
|
||||
|
||||
- name: Autobuild
|
||||
if: matrix.needs_autobuild
|
||||
uses: github/codeql-action/autobuild@b25d0ebf40e5b63ee81e1bd6e5d2a12b7c2aeb61 # v4
|
||||
|
||||
- name: Build Android for CodeQL
|
||||
if: matrix.language == 'java-kotlin'
|
||||
working-directory: apps/android
|
||||
run: ./gradlew --no-daemon :app:assemblePlayDebug
|
||||
|
||||
- name: Build Swift for CodeQL
|
||||
if: matrix.language == 'swift'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
swift build --package-path apps/macos --configuration release
|
||||
cd apps/ios
|
||||
xcodegen generate
|
||||
xcodebuild build \
|
||||
-project OpenClaw.xcodeproj \
|
||||
-scheme OpenClaw \
|
||||
-destination "generic/platform=iOS Simulator" \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
uses: github/codeql-action/analyze@b25d0ebf40e5b63ee81e1bd6e5d2a12b7c2aeb61 # v4
|
||||
with:
|
||||
category: "/codeql-critical-security/${{ matrix.category }}"
|
||||
category: "/language:${{ matrix.analyze_category }}"
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
all_locales_json='["zh-CN","zh-TW","pt-BR","de","es","ja-JP","ko","fr","ar","it","tr","uk","id","pl","th","vi","nl","fa"]'
|
||||
all_locales_json='["zh-CN","zh-TW","pt-BR","de","es","ja-JP","ko","fr","tr","uk","id","pl","th"]'
|
||||
|
||||
if [ "$EVENT_NAME" != "push" ]; then
|
||||
echo "has_locales=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
172
.github/workflows/docker-release.yml
vendored
172
.github/workflows/docker-release.yml
vendored
@@ -55,7 +55,6 @@ jobs:
|
||||
# WARNING: KEEP MANUAL BACKFILLS GATED BY THE docker-release ENVIRONMENT.
|
||||
runs-on: ubuntu-24.04
|
||||
environment: docker-release
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Approve Docker backfill
|
||||
env:
|
||||
@@ -64,7 +63,7 @@ jobs:
|
||||
|
||||
# KEEP THIS WORKFLOW ON GITHUB-HOSTED RUNNERS.
|
||||
# DO NOT MOVE IT BACK TO BLACKSMITH WITHOUT RE-VALIDATING TAG BUILDS AND BACKFILLS.
|
||||
# Build amd64 image. Default and slim tags point to the same slim runtime.
|
||||
# Build amd64 images (default + slim share the build stage cache)
|
||||
build-amd64:
|
||||
needs: [approve_manual_backfill]
|
||||
if: ${{ always() && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }}
|
||||
@@ -75,6 +74,7 @@ jobs:
|
||||
contents: read
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
slim-digest: ${{ steps.build-slim.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -117,7 +117,12 @@ jobs:
|
||||
fi
|
||||
{
|
||||
echo "value<<EOF"
|
||||
printf "%s\n" "${tags[@]}" "${slim_tags[@]}"
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -154,15 +159,28 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
cache-from: type=gha,scope=docker-release-amd64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-amd64
|
||||
build-args: |
|
||||
OPENCLAW_EXTENSIONS=diagnostics-otel
|
||||
tags: ${{ steps.tags.outputs.value }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Build arm64 image. Default and slim tags point to the same slim runtime.
|
||||
- name: Build and push amd64 slim image
|
||||
id: build-slim
|
||||
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
cache-from: type=gha,scope=docker-release-amd64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-amd64
|
||||
build-args: |
|
||||
OPENCLAW_VARIANT=slim
|
||||
tags: ${{ steps.tags.outputs.slim }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Build arm64 images (default + slim share the build stage cache)
|
||||
build-arm64:
|
||||
needs: [approve_manual_backfill]
|
||||
if: ${{ always() && (github.event_name != 'workflow_dispatch' || needs.approve_manual_backfill.result == 'success') }}
|
||||
@@ -173,6 +191,7 @@ jobs:
|
||||
contents: read
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
slim-digest: ${{ steps.build-slim.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -215,7 +234,12 @@ jobs:
|
||||
fi
|
||||
{
|
||||
echo "value<<EOF"
|
||||
printf "%s\n" "${tags[@]}" "${slim_tags[@]}"
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -252,12 +276,25 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
cache-from: type=gha,scope=docker-release-arm64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-arm64
|
||||
build-args: |
|
||||
OPENCLAW_EXTENSIONS=diagnostics-otel
|
||||
tags: ${{ steps.tags.outputs.value }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
- name: Build and push arm64 slim image
|
||||
id: build-slim
|
||||
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
cache-from: type=gha,scope=docker-release-arm64
|
||||
cache-to: type=gha,mode=max,scope=docker-release-arm64
|
||||
build-args: |
|
||||
OPENCLAW_VARIANT=slim
|
||||
tags: ${{ steps.tags.outputs.slim }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Create multi-platform manifests
|
||||
@@ -314,11 +351,16 @@ jobs:
|
||||
fi
|
||||
{
|
||||
echo "value<<EOF"
|
||||
printf "%s\n" "${tags[@]}" "${slim_tags[@]}"
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create and push manifest
|
||||
- name: Create and push default manifest
|
||||
shell: bash
|
||||
env:
|
||||
TAGS: ${{ steps.tags.outputs.value }}
|
||||
@@ -336,94 +378,20 @@ jobs:
|
||||
"${AMD64_DIGEST}" \
|
||||
"${ARM64_DIGEST}"
|
||||
|
||||
verify-attestations:
|
||||
needs: [create-manifest]
|
||||
if: ${{ always() && needs.create-manifest.result == 'success' }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up Docker Builder
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Resolve image refs
|
||||
id: refs
|
||||
- name: Create and push slim manifest
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
SOURCE_REF: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}
|
||||
IS_MANUAL_BACKFILL: ${{ github.event_name == 'workflow_dispatch' && '1' || '0' }}
|
||||
SLIM_TAGS: ${{ steps.tags.outputs.slim }}
|
||||
AMD64_SLIM_DIGEST: ${{ needs.build-amd64.outputs.slim-digest }}
|
||||
ARM64_SLIM_DIGEST: ${{ needs.build-arm64.outputs.slim-digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
multi_refs=()
|
||||
slim_multi_refs=()
|
||||
amd64_refs=()
|
||||
arm64_refs=()
|
||||
if [[ "${SOURCE_REF}" == "refs/heads/main" ]]; then
|
||||
multi_refs+=("${IMAGE}:main")
|
||||
slim_multi_refs+=("${IMAGE}:main-slim")
|
||||
amd64_refs+=("${IMAGE}:main-amd64" "${IMAGE}:main-slim-amd64")
|
||||
arm64_refs+=("${IMAGE}:main-arm64" "${IMAGE}:main-slim-arm64")
|
||||
fi
|
||||
if [[ "${SOURCE_REF}" == refs/tags/v* ]]; then
|
||||
version="${SOURCE_REF#refs/tags/v}"
|
||||
multi_refs+=("${IMAGE}:${version}")
|
||||
slim_multi_refs+=("${IMAGE}:${version}-slim")
|
||||
amd64_refs+=("${IMAGE}:${version}-amd64" "${IMAGE}:${version}-slim-amd64")
|
||||
arm64_refs+=("${IMAGE}:${version}-arm64" "${IMAGE}:${version}-slim-arm64")
|
||||
if [[ "${IS_MANUAL_BACKFILL}" != "1" && "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]]; then
|
||||
multi_refs+=("${IMAGE}:latest")
|
||||
slim_multi_refs+=("${IMAGE}:slim")
|
||||
fi
|
||||
fi
|
||||
if [[ ${#multi_refs[@]} -eq 0 || ${#amd64_refs[@]} -eq 0 || ${#arm64_refs[@]} -eq 0 ]]; then
|
||||
echo "::error::No Docker image refs resolved for ref ${SOURCE_REF}"
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "multi<<EOF"
|
||||
printf "%s\n" "${multi_refs[@]}" "${slim_multi_refs[@]}"
|
||||
echo "EOF"
|
||||
echo "amd64<<EOF"
|
||||
printf "%s\n" "${amd64_refs[@]}"
|
||||
echo "EOF"
|
||||
echo "arm64<<EOF"
|
||||
printf "%s\n" "${arm64_refs[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify Docker attestations
|
||||
shell: bash
|
||||
env:
|
||||
MULTI_REFS: ${{ steps.refs.outputs.multi }}
|
||||
AMD64_REFS: ${{ steps.refs.outputs.amd64 }}
|
||||
ARM64_REFS: ${{ steps.refs.outputs.arm64 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mapfile -t multi_refs <<< "${MULTI_REFS}"
|
||||
mapfile -t amd64_refs <<< "${AMD64_REFS}"
|
||||
mapfile -t arm64_refs <<< "${ARM64_REFS}"
|
||||
|
||||
node scripts/verify-docker-attestations.mjs \
|
||||
--platform linux/amd64 \
|
||||
--platform linux/arm64 \
|
||||
"${multi_refs[@]}"
|
||||
node scripts/verify-docker-attestations.mjs \
|
||||
--platform linux/amd64 \
|
||||
"${amd64_refs[@]}"
|
||||
node scripts/verify-docker-attestations.mjs \
|
||||
--platform linux/arm64 \
|
||||
"${arm64_refs[@]}"
|
||||
mapfile -t tags <<< "${SLIM_TAGS}"
|
||||
args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
[ -z "$tag" ] && continue
|
||||
args+=("-t" "$tag")
|
||||
done
|
||||
docker buildx imagetools create "${args[@]}" \
|
||||
"${AMD64_SLIM_DIGEST}" \
|
||||
"${ARM64_SLIM_DIGEST}"
|
||||
|
||||
5
.github/workflows/docs-agent.yml
vendored
5
.github/workflows/docs-agent.yml
vendored
@@ -149,7 +149,7 @@ jobs:
|
||||
|
||||
- name: Run Codex docs agent
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
|
||||
uses: openai/codex-action@v1
|
||||
env:
|
||||
DOCS_AGENT_BASE_SHA: ${{ steps.gate.outputs.review_base_sha }}
|
||||
DOCS_AGENT_HEAD_SHA: ${{ steps.gate.outputs.review_head_sha }}
|
||||
@@ -197,8 +197,7 @@ jobs:
|
||||
|
||||
- name: Restore Node 24 path
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
run:
|
||||
| # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job
|
||||
run: | # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job
|
||||
set -euo pipefail
|
||||
export PATH="${NODE_BIN}:${PATH}"
|
||||
echo "${NODE_BIN}" >> "$GITHUB_PATH"
|
||||
|
||||
@@ -20,7 +20,6 @@ jobs:
|
||||
set -euo pipefail
|
||||
for event_type in \
|
||||
translate-zh-cn-release \
|
||||
translate-zh-tw-release \
|
||||
translate-ja-jp-release \
|
||||
translate-es-release \
|
||||
translate-pt-br-release \
|
||||
@@ -29,9 +28,6 @@ jobs:
|
||||
translate-fr-release \
|
||||
translate-ar-release \
|
||||
translate-it-release \
|
||||
translate-vi-release \
|
||||
translate-nl-release \
|
||||
translate-fa-release \
|
||||
translate-tr-release \
|
||||
translate-uk-release \
|
||||
translate-id-release \
|
||||
|
||||
753
.github/workflows/full-release-validation.yml
vendored
753
.github/workflows/full-release-validation.yml
vendored
@@ -1,753 +0,0 @@
|
||||
name: Full Release Validation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Branch, tag, or full commit SHA to validate
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
provider:
|
||||
description: Provider lane for cross-OS onboarding and the end-to-end agent turn
|
||||
required: false
|
||||
default: openai
|
||||
type: choice
|
||||
options:
|
||||
- openai
|
||||
- anthropic
|
||||
- minimax
|
||||
mode:
|
||||
description: Which cross-OS release lanes to run
|
||||
required: false
|
||||
default: both
|
||||
type: choice
|
||||
options:
|
||||
- fresh
|
||||
- upgrade
|
||||
- both
|
||||
release_profile:
|
||||
description: Release coverage profile for live/Docker/provider breadth
|
||||
required: false
|
||||
default: full
|
||||
type: choice
|
||||
options:
|
||||
- minimum
|
||||
- stable
|
||||
- full
|
||||
rerun_group:
|
||||
description: Validation group to run
|
||||
required: false
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- ci
|
||||
- plugin-prerelease
|
||||
- release-checks
|
||||
- install-smoke
|
||||
- cross-os
|
||||
- live-e2e
|
||||
- package
|
||||
- qa
|
||||
- qa-parity
|
||||
- qa-live
|
||||
- npm-telegram
|
||||
live_suite_filter:
|
||||
description: Optional exact live suite id for focused live/E2E reruns; blank runs all selected live suites
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
npm_telegram_package_spec:
|
||||
description: Optional published package spec for the post-publish Telegram E2E lane
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
evidence_package_spec:
|
||||
description: Optional published package spec to prove in the private release evidence report
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
npm_telegram_provider_mode:
|
||||
description: Provider mode for the optional post-publish Telegram E2E lane
|
||||
required: false
|
||||
default: mock-openai
|
||||
type: choice
|
||||
options:
|
||||
- mock-openai
|
||||
- live-frontier
|
||||
npm_telegram_scenario:
|
||||
description: Optional comma-separated Telegram scenario ids for the post-publish lane
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: full-release-validation-${{ inputs.ref }}-${{ inputs.rerun_group }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
GH_REPO: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
resolve_target:
|
||||
name: Resolve target ref
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
sha: ${{ steps.resolve.outputs.sha }}
|
||||
steps:
|
||||
- name: Checkout trusted workflow helper
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
path: workflow
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Resolve target SHA
|
||||
id: resolve
|
||||
env:
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
run: |
|
||||
bash workflow/scripts/github/resolve-openclaw-ref.sh \
|
||||
--ref "$TARGET_REF" \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Summarize target
|
||||
env:
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
TARGET_SHA: ${{ steps.resolve.outputs.sha }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
EVIDENCE_PACKAGE_SPEC: ${{ inputs.evidence_package_spec }}
|
||||
RERUN_GROUP: ${{ inputs.rerun_group }}
|
||||
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
|
||||
run: |
|
||||
{
|
||||
echo "## Full release validation"
|
||||
echo
|
||||
echo "- Target ref: \`${TARGET_REF}\`"
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
echo "- Child workflow ref: \`${CHILD_WORKFLOW_REF}\`"
|
||||
echo "- Rerun group: \`${RERUN_GROUP}\`"
|
||||
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
|
||||
echo "- Live suite filter: \`${LIVE_SUITE_FILTER}\`"
|
||||
fi
|
||||
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "ci" ]]; then
|
||||
echo "- Normal CI: \`CI\` with \`target_ref=${TARGET_SHA}\`"
|
||||
else
|
||||
echo "- Normal CI: skipped by rerun group"
|
||||
fi
|
||||
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "plugin-prerelease" ]]; then
|
||||
echo "- Plugin prerelease: \`Plugin Prerelease\` with \`target_ref=${TARGET_SHA}\`"
|
||||
else
|
||||
echo "- Plugin prerelease: skipped by rerun group"
|
||||
fi
|
||||
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "release-checks" || "$RERUN_GROUP" == "install-smoke" || "$RERUN_GROUP" == "cross-os" || "$RERUN_GROUP" == "live-e2e" || "$RERUN_GROUP" == "package" || "$RERUN_GROUP" == "qa" || "$RERUN_GROUP" == "qa-parity" || "$RERUN_GROUP" == "qa-live" ]]; then
|
||||
echo "- Release/live/Docker/package/QA: \`OpenClaw Release Checks\`"
|
||||
else
|
||||
echo "- Release/live/Docker/package/QA: skipped by rerun group"
|
||||
fi
|
||||
if [[ -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" ]]; then
|
||||
echo "- Post-publish Telegram E2E: \`${NPM_TELEGRAM_PACKAGE_SPEC}\`"
|
||||
else
|
||||
echo "- Post-publish Telegram E2E: skipped because no published package spec was provided"
|
||||
fi
|
||||
if [[ -n "${EVIDENCE_PACKAGE_SPEC// }" ]]; then
|
||||
echo "- Private evidence package proof: \`${EVIDENCE_PACKAGE_SPEC}\`"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
normal_ci:
|
||||
name: Run normal full CI
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","ci"]'), inputs.rerun_group)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 240
|
||||
outputs:
|
||||
run_id: ${{ steps.dispatch.outputs.run_id }}
|
||||
url: ${{ steps.dispatch.outputs.url }}
|
||||
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
||||
steps:
|
||||
- name: Dispatch and monitor CI
|
||||
id: dispatch
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
shift
|
||||
|
||||
local before_json dispatch_output run_id status conclusion url
|
||||
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "${run_id:-}" ]]; then
|
||||
echo "Could not find dispatched run for ${workflow}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
trap - EXIT INT TERM
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "${workflow} finished with ${conclusion}: ${url}"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
||||
fi
|
||||
}
|
||||
|
||||
{
|
||||
echo "### Normal CI"
|
||||
echo
|
||||
echo "- Target ref: \`${TARGET_REF}\`"
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
dispatch_and_wait ci.yml -f target_ref="$TARGET_SHA" -f include_android=true
|
||||
|
||||
plugin_prerelease:
|
||||
name: Run plugin prerelease validation
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","plugin-prerelease"]'), inputs.rerun_group)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 300
|
||||
outputs:
|
||||
run_id: ${{ steps.dispatch.outputs.run_id }}
|
||||
url: ${{ steps.dispatch.outputs.url }}
|
||||
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
||||
steps:
|
||||
- name: Dispatch and monitor plugin prerelease
|
||||
id: dispatch
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
shift
|
||||
|
||||
local before_json dispatch_output run_id status conclusion url
|
||||
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "${run_id:-}" ]]; then
|
||||
echo "Could not find dispatched run for ${workflow}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
trap - EXIT INT TERM
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "${workflow} finished with ${conclusion}: ${url}"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
||||
fi
|
||||
}
|
||||
|
||||
{
|
||||
echo "### Plugin prerelease"
|
||||
echo
|
||||
echo "- Target ref: \`${TARGET_REF}\`"
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
dispatch_and_wait plugin-prerelease.yml -f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true
|
||||
|
||||
release_checks:
|
||||
name: Run release/live/Docker/QA validation
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","release-checks","install-smoke","cross-os","live-e2e","package","qa","qa-parity","qa-live"]'), inputs.rerun_group)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 720
|
||||
outputs:
|
||||
run_id: ${{ steps.dispatch.outputs.run_id }}
|
||||
url: ${{ steps.dispatch.outputs.url }}
|
||||
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
||||
steps:
|
||||
- name: Dispatch and monitor release checks
|
||||
id: dispatch
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
PROVIDER: ${{ inputs.provider }}
|
||||
MODE: ${{ inputs.mode }}
|
||||
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
||||
RERUN_GROUP: ${{ inputs.rerun_group }}
|
||||
LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
shift
|
||||
|
||||
local before_json dispatch_output run_id status conclusion url
|
||||
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "${run_id:-}" ]]; then
|
||||
echo "Could not find dispatched run for ${workflow}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
trap - EXIT INT TERM
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "${workflow} finished with ${conclusion}: ${url}"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
||||
fi
|
||||
}
|
||||
|
||||
{
|
||||
echo "### Release/live/Docker/QA validation"
|
||||
echo
|
||||
echo "- Target ref: \`${TARGET_REF}\`"
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
echo "- Provider: \`${PROVIDER}\`"
|
||||
echo "- Cross-OS mode: \`${MODE}\`"
|
||||
echo "- Release profile: \`${RELEASE_PROFILE}\`"
|
||||
echo "- Rerun group: \`${RERUN_GROUP}\`"
|
||||
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
|
||||
echo "- Live suite filter: \`${LIVE_SUITE_FILTER}\`"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
child_rerun_group="$RERUN_GROUP"
|
||||
if [[ "$child_rerun_group" == "release-checks" ]]; then
|
||||
child_rerun_group=all
|
||||
fi
|
||||
|
||||
args=(
|
||||
-f ref="$TARGET_SHA"
|
||||
-f expected_sha="$TARGET_SHA"
|
||||
-f provider="$PROVIDER"
|
||||
-f mode="$MODE"
|
||||
-f release_profile="$RELEASE_PROFILE"
|
||||
-f rerun_group="$child_rerun_group"
|
||||
)
|
||||
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
|
||||
args+=(-f live_suite_filter="$LIVE_SUITE_FILTER")
|
||||
fi
|
||||
|
||||
dispatch_and_wait openclaw-release-checks.yml "${args[@]}"
|
||||
|
||||
npm_telegram:
|
||||
name: Run post-publish Telegram E2E
|
||||
needs: [resolve_target]
|
||||
if: inputs.npm_telegram_package_spec != '' && contains(fromJSON('["all","npm-telegram"]'), inputs.rerun_group)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
outputs:
|
||||
run_id: ${{ steps.dispatch.outputs.run_id }}
|
||||
url: ${{ steps.dispatch.outputs.url }}
|
||||
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
||||
steps:
|
||||
- name: Dispatch and monitor npm Telegram E2E
|
||||
id: dispatch
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||
PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
||||
PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
||||
SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
before_json="$(gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
args=(-f package_spec="$PACKAGE_SPEC" -f harness_ref="$TARGET_SHA" -f provider_mode="$PROVIDER_MODE")
|
||||
if [[ -n "${SCENARIO// }" ]]; then
|
||||
args+=(-f scenario="$SCENARIO")
|
||||
fi
|
||||
|
||||
gh workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}"
|
||||
|
||||
run_id=""
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "Could not find dispatched run for npm-telegram-beta-e2e.yml." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
trap - EXIT INT TERM
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "npm-telegram-beta-e2e.yml finished with ${conclusion}: ${url}"
|
||||
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
||||
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
||||
fi
|
||||
|
||||
summary:
|
||||
name: Verify full validation
|
||||
needs: [normal_ci, plugin_prerelease, release_checks, npm_telegram]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Request private evidence update
|
||||
env:
|
||||
RELEASE_PRIVATE_DISPATCH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN }}
|
||||
TARGET_REF: ${{ inputs.ref }}
|
||||
PACKAGE_SPEC: ${{ inputs.evidence_package_spec || inputs.npm_telegram_package_spec }}
|
||||
GITHUB_RUN_ID_VALUE: ${{ github.run_id }}
|
||||
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$RELEASE_CHECKS_RESULT" == "skipped" ]]; then
|
||||
echo "Release checks were skipped by rerun group; skipping automatic private evidence update."
|
||||
exit 0
|
||||
fi
|
||||
if [[ -z "${RELEASE_PRIVATE_DISPATCH_TOKEN// }" ]]; then
|
||||
echo "OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN is not configured; skipping automatic private evidence update."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
release_id="${TARGET_REF#refs/tags/}"
|
||||
release_id="${release_id#v}"
|
||||
if [[ "$PACKAGE_SPEC" =~ ^openclaw@(.+)$ ]]; then
|
||||
release_id="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
release_id="$(printf '%s' "$release_id" | tr '/:@ ' '----' | tr -cd 'A-Za-z0-9._-')"
|
||||
if [[ -z "$release_id" ]]; then
|
||||
echo "::error::Could not derive release evidence id from target ref '${TARGET_REF}'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
payload="$(
|
||||
jq -cn \
|
||||
--arg full_validation_run_id "$GITHUB_RUN_ID_VALUE" \
|
||||
--arg release_id "$release_id" \
|
||||
--arg release_ref "$TARGET_REF" \
|
||||
--arg package_spec "$PACKAGE_SPEC" \
|
||||
--arg notes "Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after child workflows completed; the parent summary re-checks current child run conclusions." \
|
||||
'{
|
||||
event_type: "openclaw_full_release_validation_completed",
|
||||
client_payload: {
|
||||
full_validation_run_id: $full_validation_run_id,
|
||||
release_id: $release_id,
|
||||
release_ref: $release_ref,
|
||||
package_spec: $package_spec,
|
||||
notes: $notes
|
||||
}
|
||||
}'
|
||||
)"
|
||||
|
||||
curl --fail-with-body \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${RELEASE_PRIVATE_DISPATCH_TOKEN}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/openclaw/releases-private/dispatches \
|
||||
-d "$payload"
|
||||
|
||||
- name: Verify child workflow results
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
NORMAL_CI_RUN_ID: ${{ needs.normal_ci.outputs.run_id }}
|
||||
PLUGIN_PRERELEASE_RUN_ID: ${{ needs.plugin_prerelease.outputs.run_id }}
|
||||
RELEASE_CHECKS_RUN_ID: ${{ needs.release_checks.outputs.run_id }}
|
||||
NPM_TELEGRAM_RUN_ID: ${{ needs.npm_telegram.outputs.run_id }}
|
||||
NORMAL_CI_RESULT: ${{ needs.normal_ci.result }}
|
||||
PLUGIN_PRERELEASE_RESULT: ${{ needs.plugin_prerelease.result }}
|
||||
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
||||
NPM_TELEGRAM_RESULT: ${{ needs.npm_telegram.result }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
check_child() {
|
||||
local label="$1"
|
||||
local run_id="$2"
|
||||
local required="$3"
|
||||
|
||||
if [[ -z "${run_id// }" ]]; then
|
||||
if [[ "$required" == "0" ]]; then
|
||||
echo "${label}: skipped"
|
||||
return 0
|
||||
fi
|
||||
echo "::error::${label} did not record a child run id."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local run_json status conclusion url attempt
|
||||
run_json="$(gh run view "$run_id" --json status,conclusion,url,attempt,jobs)"
|
||||
status="$(jq -r '.status' <<< "$run_json")"
|
||||
conclusion="$(jq -r '.conclusion' <<< "$run_json")"
|
||||
url="$(jq -r '.url' <<< "$run_json")"
|
||||
attempt="$(jq -r '.attempt' <<< "$run_json")"
|
||||
echo "${label}: ${status}/${conclusion} attempt ${attempt}: ${url}"
|
||||
|
||||
if [[ "$status" != "completed" || "$conclusion" != "success" ]]; then
|
||||
echo "::error::${label} child run ended with ${status}/${conclusion}: ${url}"
|
||||
jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, status, conclusion, url}' <<< "$run_json" || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
append_child_overview() {
|
||||
{
|
||||
echo
|
||||
echo "### Child workflow overview"
|
||||
echo
|
||||
echo "| Child | Result | Minutes | Run |"
|
||||
echo "| --- | --- | ---: | --- |"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
append_child_row() {
|
||||
local label="$1"
|
||||
local run_id="$2"
|
||||
local result="$3"
|
||||
|
||||
if [[ -z "${run_id// }" ]]; then
|
||||
echo "| \`${label}\` | \`${result}\` | | skipped |" >> "$GITHUB_STEP_SUMMARY"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local run_json row
|
||||
run_json="$(gh run view "$run_id" --json status,conclusion,url,createdAt,updatedAt)"
|
||||
row="$(
|
||||
jq -r --arg label "$label" '
|
||||
def ts: fromdateiso8601;
|
||||
. as $run |
|
||||
($run.createdAt // "") as $created |
|
||||
($run.updatedAt // "") as $updated |
|
||||
(if ($created | length) > 0 and ($updated | length) > 0
|
||||
then (((($updated | ts) - ($created | ts)) / 60) * 10 | round / 10 | tostring)
|
||||
else ""
|
||||
end) as $minutes |
|
||||
"| `" + $label + "` | `" + ($run.status // "") + "/" + ($run.conclusion // "") + "` | " + $minutes + " | [run](" + ($run.url // "") + ") |"
|
||||
' <<< "$run_json"
|
||||
)"
|
||||
echo "$row" >> "$GITHUB_STEP_SUMMARY"
|
||||
}
|
||||
|
||||
append_child_row "normal_ci" "$NORMAL_CI_RUN_ID" "$NORMAL_CI_RESULT"
|
||||
append_child_row "plugin_prerelease" "$PLUGIN_PRERELEASE_RUN_ID" "$PLUGIN_PRERELEASE_RESULT"
|
||||
append_child_row "release_checks" "$RELEASE_CHECKS_RUN_ID" "$RELEASE_CHECKS_RESULT"
|
||||
append_child_row "npm_telegram" "$NPM_TELEGRAM_RUN_ID" "$NPM_TELEGRAM_RESULT"
|
||||
}
|
||||
|
||||
summarize_child_timing() {
|
||||
local label="$1"
|
||||
local run_id="$2"
|
||||
if [[ -z "${run_id// }" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
{
|
||||
echo
|
||||
echo "### Slowest jobs: ${label}"
|
||||
echo
|
||||
gh run view "$run_id" --json jobs --jq '
|
||||
def ts: fromdateiso8601;
|
||||
"| Job | Result | Minutes |",
|
||||
"| --- | --- | ---: |",
|
||||
([.jobs[]
|
||||
| select(.startedAt != "0001-01-01T00:00:00Z" and .completedAt != "0001-01-01T00:00:00Z")
|
||||
| . + {durationMin: ((((.completedAt | ts) - (.startedAt | ts)) / 60) * 10 | round / 10)}
|
||||
| {name, conclusion, durationMin}]
|
||||
| sort_by(.durationMin)
|
||||
| reverse
|
||||
| .[0:10]
|
||||
| map("| `" + (.name | gsub("\\|"; "\\|")) + "` | `" + ((.conclusion // "") | tostring) + "` | " + (.durationMin | tostring) + " |")
|
||||
| .[])
|
||||
' || echo "_Unable to summarize jobs for run ${run_id}._"
|
||||
echo
|
||||
echo "### Longest queues: ${label}"
|
||||
echo
|
||||
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq ".jobs[] | @json" | jq -sr '
|
||||
def ts: fromdateiso8601;
|
||||
"| Job | Result | Queue minutes | Run minutes |",
|
||||
"| --- | --- | ---: | ---: |",
|
||||
([.[]
|
||||
| select(.created_at != null and .started_at != null)
|
||||
| . + {
|
||||
queueMin: ((((.started_at | ts) - (.created_at | ts)) / 60) * 10 | round / 10),
|
||||
durationMin: (if .completed_at == null then null else ((((.completed_at | ts) - (.started_at | ts)) / 60) * 10 | round / 10) end)
|
||||
}
|
||||
| select(.queueMin > 0)
|
||||
| {name, conclusion, queueMin, durationMin}]
|
||||
| sort_by(.queueMin)
|
||||
| reverse
|
||||
| .[0:10]
|
||||
| map("| `" + (.name | gsub("\\|"; "\\|")) + "` | `" + ((.conclusion // "") | tostring) + "` | " + (.queueMin | tostring) + " | " + ((.durationMin // "") | tostring) + " |")
|
||||
| .[])
|
||||
' || echo "_Unable to summarize queue times for run ${run_id}._"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
}
|
||||
|
||||
failed=0
|
||||
|
||||
append_child_overview
|
||||
|
||||
if [[ "$NORMAL_CI_RESULT" == "skipped" && -z "${NORMAL_CI_RUN_ID// }" ]]; then
|
||||
check_child "normal_ci" "" 0 || failed=1
|
||||
else
|
||||
check_child "normal_ci" "$NORMAL_CI_RUN_ID" 1 || failed=1
|
||||
fi
|
||||
|
||||
if [[ "$PLUGIN_PRERELEASE_RESULT" == "skipped" && -z "${PLUGIN_PRERELEASE_RUN_ID// }" ]]; then
|
||||
check_child "plugin_prerelease" "" 0 || failed=1
|
||||
else
|
||||
check_child "plugin_prerelease" "$PLUGIN_PRERELEASE_RUN_ID" 1 || failed=1
|
||||
fi
|
||||
|
||||
if [[ "$RELEASE_CHECKS_RESULT" == "skipped" && -z "${RELEASE_CHECKS_RUN_ID// }" ]]; then
|
||||
check_child "release_checks" "" 0 || failed=1
|
||||
else
|
||||
check_child "release_checks" "$RELEASE_CHECKS_RUN_ID" 1 || failed=1
|
||||
fi
|
||||
|
||||
if [[ "$NPM_TELEGRAM_RESULT" == "skipped" && -z "${NPM_TELEGRAM_RUN_ID// }" ]]; then
|
||||
check_child "npm_telegram" "" 0 || failed=1
|
||||
else
|
||||
check_child "npm_telegram" "$NPM_TELEGRAM_RUN_ID" 1 || failed=1
|
||||
fi
|
||||
|
||||
summarize_child_timing "normal_ci" "$NORMAL_CI_RUN_ID"
|
||||
summarize_child_timing "plugin_prerelease" "$PLUGIN_PRERELEASE_RUN_ID"
|
||||
summarize_child_timing "release_checks" "$RELEASE_CHECKS_RUN_ID"
|
||||
summarize_child_timing "npm_telegram" "$NPM_TELEGRAM_RUN_ID"
|
||||
|
||||
exit "$failed"
|
||||
307
.github/workflows/install-smoke.yml
vendored
307
.github/workflows/install-smoke.yml
vendored
@@ -10,11 +10,6 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
update_baseline_version:
|
||||
description: Baseline openclaw version or dist-tag for installer update smoke
|
||||
required: false
|
||||
default: latest
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
@@ -26,19 +21,13 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
update_baseline_version:
|
||||
description: Baseline openclaw version or dist-tag for installer update smoke
|
||||
required: false
|
||||
default: latest
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && format('{0}-{1}-{2}', github.workflow, github.event_name, github.run_id) || format('{0}-{1}', github.workflow, github.ref) }}
|
||||
cancel-in-progress: ${{ github.event_name != 'workflow_call' }}
|
||||
group: ${{ github.event_name == 'workflow_dispatch' && format('{0}-manual-{1}', github.workflow, github.run_id) || format('{0}-{1}', github.workflow, github.ref) }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -52,9 +41,6 @@ jobs:
|
||||
run_fast_install_smoke: ${{ steps.manifest.outputs.run_fast_install_smoke }}
|
||||
run_full_install_smoke: ${{ steps.manifest.outputs.run_full_install_smoke }}
|
||||
run_bun_global_install_smoke: ${{ steps.manifest.outputs.run_bun_global_install_smoke }}
|
||||
target_sha: ${{ steps.manifest.outputs.target_sha }}
|
||||
dockerfile_image: ${{ steps.manifest.outputs.dockerfile_image }}
|
||||
dockerfile_cache_scope: ${{ steps.manifest.outputs.dockerfile_cache_scope }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -78,10 +64,6 @@ jobs:
|
||||
run_full_install_smoke=true
|
||||
run_bun_global_install_smoke=false
|
||||
run_install_smoke=true
|
||||
target_sha="$(git rev-parse HEAD)"
|
||||
owner="$(printf '%s' "${GITHUB_REPOSITORY_OWNER:-openclaw}" | tr '[:upper:]' '[:lower:]')"
|
||||
dockerfile_image="ghcr.io/${owner}/openclaw-dockerfile-smoke:${target_sha}"
|
||||
dockerfile_cache_scope="openclaw-dockerfile-smoke"
|
||||
if [ "$event_name" = "schedule" ]; then
|
||||
run_bun_global_install_smoke=true
|
||||
elif [ "$event_name" = "workflow_dispatch" ] || [ "$event_name" = "workflow_call" ]; then
|
||||
@@ -95,9 +77,6 @@ jobs:
|
||||
echo "run_fast_install_smoke=$run_fast_install_smoke"
|
||||
echo "run_full_install_smoke=$run_full_install_smoke"
|
||||
echo "run_bun_global_install_smoke=$run_bun_global_install_smoke"
|
||||
echo "target_sha=$target_sha"
|
||||
echo "dockerfile_image=$dockerfile_image"
|
||||
echo "dockerfile_cache_scope=$dockerfile_cache_scope"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
install-smoke-fast:
|
||||
@@ -115,40 +94,27 @@ jobs:
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
# Keep release smoke builds bounded and log-producing. The Blacksmith
|
||||
# build action can leave jobs in-progress without step logs when a remote
|
||||
# builder stalls; an explicit buildx invocation fails closed instead.
|
||||
# Blacksmith's builder owns the Docker layer cache; keep smoke builds off
|
||||
# explicit gha cache directives so local tags still load cleanly.
|
||||
- name: Build root Dockerfile smoke image
|
||||
run: |
|
||||
timeout 45m docker buildx build \
|
||||
--progress=plain \
|
||||
--load \
|
||||
--build-arg OPENCLAW_EXTENSIONS=matrix \
|
||||
-t openclaw-dockerfile-smoke:local \
|
||||
-t openclaw-ext-smoke:local \
|
||||
-f ./Dockerfile \
|
||||
.
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: |
|
||||
OPENCLAW_DOCKER_APT_UPGRADE=0
|
||||
OPENCLAW_EXTENSIONS=matrix
|
||||
tags: |
|
||||
openclaw-dockerfile-smoke:local
|
||||
openclaw-ext-smoke:local
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
|
||||
- name: Run root Dockerfile CLI smoke
|
||||
run: |
|
||||
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc '
|
||||
which openclaw &&
|
||||
openclaw --version &&
|
||||
node -e "
|
||||
const fs = require(\"node:fs\");
|
||||
const path = require(\"node:path\");
|
||||
const pkg = require(\"/app/package.json\");
|
||||
for (const [dep, rel] of Object.entries(pkg.pnpm?.patchedDependencies ?? {})) {
|
||||
const absolute = path.join(\"/app\", rel);
|
||||
if (!fs.existsSync(absolute)) {
|
||||
throw new Error(`missing patch for ${dep}: ${rel}`);
|
||||
}
|
||||
}
|
||||
"
|
||||
'
|
||||
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'
|
||||
|
||||
- name: Run agents delete shared workspace Docker CLI smoke
|
||||
env:
|
||||
@@ -207,12 +173,10 @@ jobs:
|
||||
"
|
||||
'
|
||||
|
||||
root_dockerfile_image:
|
||||
install-smoke:
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_full_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
outputs:
|
||||
image_ref: ${{ steps.image.outputs.image_ref }}
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
@@ -222,130 +186,52 @@ jobs:
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Check for existing root Dockerfile smoke image
|
||||
id: existing
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if timeout 180s docker pull "$IMAGE_REF"; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Using existing root Dockerfile smoke image: \`$IMAGE_REF\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No existing root Dockerfile smoke image found for \`$IMAGE_REF\`; building it." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
if: steps.existing.outputs.exists != 'true'
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
# Build once with the matrix extension and publish by target SHA. Use a
|
||||
# direct buildx command so release jobs emit Docker progress and time out.
|
||||
- name: Build and push root Dockerfile smoke image
|
||||
if: steps.existing.outputs.exists != 'true'
|
||||
env:
|
||||
CACHE_SCOPE: ${{ needs.preflight.outputs.dockerfile_cache_scope }}
|
||||
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
|
||||
run: |
|
||||
timeout 45m docker buildx build \
|
||||
--progress=plain \
|
||||
--push \
|
||||
--cache-from "type=gha,scope=${CACHE_SCOPE}" \
|
||||
--cache-to "type=gha,scope=${CACHE_SCOPE},mode=max" \
|
||||
--build-arg OPENCLAW_EXTENSIONS=matrix \
|
||||
-t "$IMAGE_REF" \
|
||||
-f ./Dockerfile \
|
||||
.
|
||||
|
||||
- name: Record root image output
|
||||
id: image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
|
||||
run: echo "image_ref=$IMAGE_REF" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Summarize root image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
|
||||
TARGET_SHA: ${{ needs.preflight.outputs.target_sha }}
|
||||
run: |
|
||||
{
|
||||
echo "## Root Dockerfile smoke image"
|
||||
echo
|
||||
echo "- Target SHA: \`${TARGET_SHA}\`"
|
||||
echo "- Image: \`${IMAGE_REF}\`"
|
||||
echo "- Reused existing image: \`${{ steps.existing.outputs.exists }}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
qr_package_install_smoke:
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_full_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout CLI
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
# Blacksmith's builder owns the Docker layer cache; keep smoke builds off
|
||||
# explicit gha cache directives so local tags still load cleanly.
|
||||
- name: Run QR package install smoke
|
||||
env:
|
||||
OPENCLAW_QR_SMOKE_FORCE_INSTALL: "1"
|
||||
run: bash scripts/e2e/qr-import-docker.sh
|
||||
|
||||
root_dockerfile_smokes:
|
||||
needs: [preflight, root_dockerfile_image]
|
||||
if: needs.preflight.outputs.run_full_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout CLI
|
||||
uses: actions/checkout@v6
|
||||
# Build once with the matrix extension and tag both smoke names. This
|
||||
# keeps the build-arg coverage without a second Blacksmith build action.
|
||||
- name: Build root Dockerfile smoke image
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Pull root Dockerfile smoke image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: timeout 300s docker pull "$IMAGE_REF"
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: |
|
||||
OPENCLAW_DOCKER_APT_UPGRADE=0
|
||||
OPENCLAW_EXTENSIONS=matrix
|
||||
tags: |
|
||||
openclaw-dockerfile-smoke:local
|
||||
openclaw-ext-smoke:local
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
|
||||
- name: Run root Dockerfile CLI smoke
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: |
|
||||
docker run --rm --entrypoint sh "$IMAGE_REF" -lc 'which openclaw && openclaw --version'
|
||||
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'
|
||||
|
||||
- name: Run agents delete shared workspace Docker CLI smoke
|
||||
env:
|
||||
OPENCLAW_AGENTS_DELETE_SHARED_WORKSPACE_E2E_IMAGE: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
OPENCLAW_AGENTS_DELETE_SHARED_WORKSPACE_E2E_IMAGE: openclaw-dockerfile-smoke:local
|
||||
OPENCLAW_AGENTS_DELETE_SHARED_WORKSPACE_E2E_SKIP_BUILD: "1"
|
||||
run: bash scripts/e2e/agents-delete-shared-workspace-docker.sh
|
||||
|
||||
- name: Run Docker gateway network e2e
|
||||
env:
|
||||
OPENCLAW_GATEWAY_NETWORK_E2E_IMAGE: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
OPENCLAW_GATEWAY_NETWORK_E2E_IMAGE: openclaw-dockerfile-smoke:local
|
||||
OPENCLAW_GATEWAY_NETWORK_E2E_SKIP_BUILD: "1"
|
||||
run: bash scripts/e2e/gateway-network-docker.sh
|
||||
|
||||
- name: Smoke test Dockerfile with matrix extension build arg
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: |
|
||||
docker run --rm --entrypoint sh "$IMAGE_REF" -lc '
|
||||
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
|
||||
which openclaw &&
|
||||
openclaw --version &&
|
||||
node -e "
|
||||
@@ -388,60 +274,39 @@ jobs:
|
||||
"
|
||||
'
|
||||
|
||||
installer_smoke:
|
||||
needs: [preflight, root_dockerfile_image]
|
||||
if: needs.preflight.outputs.run_full_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
steps:
|
||||
- name: Checkout CLI
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Pull root Dockerfile smoke image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: timeout 300s docker pull "$IMAGE_REF"
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
- name: Build installer smoke image
|
||||
run: |
|
||||
timeout 20m docker buildx build \
|
||||
--progress=plain \
|
||||
--load \
|
||||
-t openclaw-install-smoke:local \
|
||||
-f ./scripts/docker/install-sh-smoke/Dockerfile \
|
||||
./scripts/docker
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: ./scripts/docker
|
||||
file: ./scripts/docker/install-sh-smoke/Dockerfile
|
||||
tags: openclaw-install-smoke:local
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
|
||||
- name: Build installer non-root image
|
||||
run: |
|
||||
timeout 20m docker buildx build \
|
||||
--progress=plain \
|
||||
--load \
|
||||
-t openclaw-install-nonroot:local \
|
||||
-f ./scripts/docker/install-sh-nonroot/Dockerfile \
|
||||
./scripts/docker
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: ./scripts/docker
|
||||
file: ./scripts/docker/install-sh-nonroot/Dockerfile
|
||||
tags: openclaw-install-nonroot:local
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
|
||||
- name: Setup Node environment for installer smoke
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
install-bun: ${{ needs.preflight.outputs.run_bun_global_install_smoke }}
|
||||
install-deps: "true"
|
||||
|
||||
- name: Run Bun global install image-provider smoke
|
||||
if: needs.preflight.outputs.run_bun_global_install_smoke == 'true'
|
||||
env:
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE: openclaw-dockerfile-smoke:local
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD: "0"
|
||||
run: bash scripts/e2e/bun-global-install-smoke.sh
|
||||
|
||||
- name: Run installer docker tests
|
||||
env:
|
||||
OPENCLAW_INSTALL_URL: https://openclaw.ai/install.sh
|
||||
@@ -453,50 +318,16 @@ jobs:
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT: "0"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL: "1"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
|
||||
OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE: ${{ inputs.update_baseline_version || 'latest' }}
|
||||
OPENCLAW_INSTALL_SMOKE_UPDATE_DIST_IMAGE: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE: latest
|
||||
OPENCLAW_INSTALL_SMOKE_UPDATE_DIST_IMAGE: openclaw-dockerfile-smoke:local
|
||||
OPENCLAW_INSTALL_SMOKE_UPDATE_SKIP_LOCAL_BUILD: "1"
|
||||
run: bash scripts/test-install-sh-docker.sh
|
||||
|
||||
bun_global_install_smoke:
|
||||
needs: [preflight, root_dockerfile_image]
|
||||
if: needs.preflight.outputs.run_full_install_smoke == 'true' && needs.preflight.outputs.run_bun_global_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Checkout CLI
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Pull root Dockerfile smoke image
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
run: timeout 300s docker pull "$IMAGE_REF"
|
||||
|
||||
- name: Setup Node environment for Bun smoke
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "true"
|
||||
install-deps: "true"
|
||||
|
||||
- name: Run Bun global install image-provider smoke
|
||||
env:
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE: ${{ needs.root_dockerfile_image.outputs.image_ref }}
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD: "0"
|
||||
run: bash scripts/e2e/bun-global-install-smoke.sh
|
||||
|
||||
docker-e2e-fast:
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_fast_install_smoke == 'true' || needs.preflight.outputs.run_full_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
timeout-minutes: 12
|
||||
timeout-minutes: 8
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
@@ -508,8 +339,6 @@ jobs:
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
- name: Setup Node environment for package smoke
|
||||
uses: ./.github/actions/setup-node-env
|
||||
@@ -521,4 +350,4 @@ jobs:
|
||||
env:
|
||||
OPENCLAW_BUNDLED_CHANNEL_DEPS_E2E_IMAGE: openclaw-bundled-channel-fast:local
|
||||
OPENCLAW_BUNDLED_CHANNEL_DOCKER_RUN_TIMEOUT: 90s
|
||||
run: timeout 480s pnpm test:docker:bundled-channel-deps:fast
|
||||
run: timeout 240s pnpm test:docker:bundled-channel-deps:fast
|
||||
|
||||
8
.github/workflows/labeler.yml
vendored
8
.github/workflows/labeler.yml
vendored
@@ -278,7 +278,6 @@ jobs:
|
||||
const labelColor = "B60205";
|
||||
const labelDescription = `Author has more than ${activePrLimit} active PRs in this repo`;
|
||||
const authorLogin = pullRequest.user?.login;
|
||||
const headRefName = pullRequest.head?.ref ?? "";
|
||||
if (!authorLogin) {
|
||||
return;
|
||||
}
|
||||
@@ -375,12 +374,7 @@ jobs:
|
||||
return false;
|
||||
};
|
||||
|
||||
const automationPrHeadPrefixes = ["clawsweeper/", "clownfish/"];
|
||||
const isAutomationPullRequest =
|
||||
typeof headRefName === "string" &&
|
||||
automationPrHeadPrefixes.some((prefix) => headRefName.startsWith(prefix));
|
||||
|
||||
if ((await isPrivilegedAuthor()) || isAutomationPullRequest) {
|
||||
if (await isPrivilegedAuthor()) {
|
||||
if (labelNames.has(activePrLimitLabel)) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
|
||||
54
.github/workflows/live-media-runner-image.yml
vendored
54
.github/workflows/live-media-runner-image.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: Live Media Runner Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/images/live-media-runner/Dockerfile"
|
||||
- ".github/workflows/live-media-runner-image.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
concurrency:
|
||||
group: live-media-runner-image-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build live media runner image
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
- name: Build and push live media runner image
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .github/images/live-media-runner
|
||||
file: .github/images/live-media-runner/Dockerfile
|
||||
platforms: linux/amd64
|
||||
tags: |
|
||||
ghcr.io/openclaw/openclaw-live-media-runner:ubuntu-24.04
|
||||
ghcr.io/openclaw/openclaw-live-media-runner:${{ github.sha }}
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
push: true
|
||||
@@ -1,99 +0,0 @@
|
||||
name: Maintainer Command Reactions
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: maintainer-command-reactions-${{ github.event.comment.id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
react:
|
||||
if: ${{ !endsWith(github.actor, '[bot]') }}
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
env:
|
||||
MAINTAINER_COMMAND_REACTIONS: ${{ vars.MAINTAINER_COMMAND_REACTIONS || '/autoclose,/clawsweeper autoclose,/clawsweeper automerge,/merge,/land,/landpr' }}
|
||||
steps:
|
||||
- name: React to maintainer slash command
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const comment = context.payload.comment;
|
||||
const issue = context.payload.issue;
|
||||
|
||||
const commands = (process.env.MAINTAINER_COMMAND_REACTIONS || "")
|
||||
.split(",")
|
||||
.map((command) => command.trim())
|
||||
.filter(Boolean);
|
||||
const commandLine = String(comment.body || "")
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find((line) => commands.some((command) => line === command || line.startsWith(`${command} `)));
|
||||
|
||||
if (!commandLine) {
|
||||
core.info(`Skipping comment ${comment.id}; no tracked maintainer command found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const isAutocloseCommand =
|
||||
commandLine === "/autoclose" ||
|
||||
commandLine.startsWith("/autoclose ") ||
|
||||
commandLine === "/clawsweeper autoclose" ||
|
||||
commandLine.startsWith("/clawsweeper autoclose ");
|
||||
if (!issue.pull_request && !isAutocloseCommand) {
|
||||
core.info("Skipping non-autoclose command reaction because the comment is not on a pull request.");
|
||||
return;
|
||||
}
|
||||
|
||||
const maintainerPermissions = new Set(["admin", "maintain", "write"]);
|
||||
let permission = "none";
|
||||
try {
|
||||
const result = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: comment.user.login,
|
||||
});
|
||||
permission = String(result.data.permission || "none").toLowerCase();
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
core.info(`Could not resolve repository permission for ${comment.user.login}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!maintainerPermissions.has(permission)) {
|
||||
core.info(
|
||||
`Skipping non-maintainer command reaction for ${comment.user.login}; repository permission is ${permission}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
async function react(content) {
|
||||
try {
|
||||
await github.rest.reactions.createForIssueComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: comment.id,
|
||||
content,
|
||||
});
|
||||
core.info(`Added ${content} reaction to comment ${comment.id}.`);
|
||||
} catch (error) {
|
||||
if (error.status === 422 && /already exists/i.test(String(error.message))) {
|
||||
core.info(`${content} reaction already exists on comment ${comment.id}.`);
|
||||
return;
|
||||
}
|
||||
if (error.status === 403 && /resource not accessible by integration/i.test(String(error.message))) {
|
||||
core.warning(`${content} reaction could not be added with this token: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await react("eyes");
|
||||
core.info(`Maintainer command observed on ${issue.pull_request ? "PR" : "issue"} #${issue.number}: ${commandLine}`);
|
||||
192
.github/workflows/npm-telegram-beta-e2e.yml
vendored
192
.github/workflows/npm-telegram-beta-e2e.yml
vendored
@@ -4,25 +4,10 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package_spec:
|
||||
description: Published OpenClaw package spec to test when no artifact is supplied
|
||||
description: Published OpenClaw package spec to test
|
||||
required: true
|
||||
default: openclaw@beta
|
||||
type: string
|
||||
package_label:
|
||||
description: Optional display label for an artifact-backed package candidate
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Advanced package-under-test artifact name; leave blank for registry install
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
harness_ref:
|
||||
description: Source ref for the private QA harness; defaults to the dispatched workflow ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
provider_mode:
|
||||
description: QA provider mode
|
||||
required: true
|
||||
@@ -35,44 +20,6 @@ on:
|
||||
description: Optional comma-separated Telegram scenario ids
|
||||
required: false
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
package_spec:
|
||||
description: Published OpenClaw package spec to test when no artifact is supplied
|
||||
required: true
|
||||
type: string
|
||||
package_artifact_name:
|
||||
description: Optional package-under-test artifact from the current workflow run
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_label:
|
||||
description: Optional display label for an artifact-backed package candidate
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
harness_ref:
|
||||
description: Source ref for the private QA harness; defaults to the called workflow ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
provider_mode:
|
||||
description: QA provider mode
|
||||
required: false
|
||||
default: mock-openai
|
||||
type: string
|
||||
scenario:
|
||||
description: Optional comma-separated Telegram scenario ids
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SITE_URL:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -87,39 +34,106 @@ env:
|
||||
PNPM_VERSION: "10.33.0"
|
||||
|
||||
jobs:
|
||||
run_package_telegram_e2e:
|
||||
name: Run package Telegram E2E
|
||||
validate_dispatch_ref:
|
||||
name: Validate dispatch ref
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Require main workflow ref
|
||||
env:
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]]; then
|
||||
echo "NPM Telegram beta E2E must be dispatched from main so workflow logic stays controlled." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
approve_release_manager:
|
||||
name: Approve npm Telegram beta E2E
|
||||
needs: validate_dispatch_ref
|
||||
runs-on: ubuntu-latest
|
||||
environment: npm-release
|
||||
steps:
|
||||
- name: Record approval
|
||||
env:
|
||||
PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
run: echo "Approved npm Telegram beta E2E for ${PACKAGE_SPEC}"
|
||||
|
||||
prepare_docker_e2e_image:
|
||||
name: Prepare Docker E2E image
|
||||
needs: validate_dispatch_ref
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
timeout-minutes: 90
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
image: ${{ steps.image.outputs.image }}
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
steps:
|
||||
- name: Checkout dispatch ref
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.harness_ref || github.sha }}
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Resolve Docker E2E image tag
|
||||
id: image
|
||||
shell: bash
|
||||
env:
|
||||
SELECTED_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
repository="${GITHUB_REPOSITORY,,}"
|
||||
image="ghcr.io/${repository}-docker-e2e:${SELECTED_SHA}"
|
||||
echo "image=$image" >> "$GITHUB_OUTPUT"
|
||||
echo "Docker E2E image: \`$image\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
- name: Build Docker E2E image
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Build and push Docker E2E image
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./scripts/e2e/Dockerfile
|
||||
target: build
|
||||
platforms: linux/amd64
|
||||
tags: openclaw-docker-e2e:local
|
||||
load: true
|
||||
push: false
|
||||
tags: ${{ steps.image.outputs.image }}
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
run_npm_telegram_beta_e2e:
|
||||
name: Run published npm Telegram E2E
|
||||
needs: [approve_release_manager, prepare_docker_e2e_image]
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
@@ -131,7 +145,6 @@ jobs:
|
||||
- name: Validate inputs and secrets
|
||||
env:
|
||||
PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
|
||||
PROVIDER_MODE: ${{ inputs.provider_mode }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
@@ -140,19 +153,10 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
||||
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
||||
echo "package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
|
||||
echo "package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
case "${PROVIDER_MODE}" in
|
||||
mock-openai | live-frontier) ;;
|
||||
*)
|
||||
echo "provider_mode must be mock-openai or live-frontier; got: ${PROVIDER_MODE}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
require_var() {
|
||||
local key="$1"
|
||||
@@ -168,31 +172,21 @@ jobs:
|
||||
require_var OPENAI_API_KEY
|
||||
fi
|
||||
|
||||
- name: Download package-under-test artifact
|
||||
if: inputs.package_artifact_name != ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.package_artifact_name }}
|
||||
path: .artifacts/telegram-package-under-test
|
||||
|
||||
- name: Run package Telegram E2E
|
||||
- name: Run npm Telegram beta E2E
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: openclaw-docker-e2e:local
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: ${{ needs.prepare_docker_e2e_image.outputs.image }}
|
||||
OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL: ${{ inputs.package_label }}
|
||||
OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.provider_mode }}
|
||||
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE: convex
|
||||
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE: ci
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
|
||||
INPUT_SCENARIO: ${{ inputs.scenario }}
|
||||
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -200,20 +194,6 @@ jobs:
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
export OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR="${output_dir}"
|
||||
|
||||
if [[ -n "${PACKAGE_ARTIFACT_NAME// }" ]]; then
|
||||
mapfile -t package_tgzs < <(find .artifacts/telegram-package-under-test -type f -name "*.tgz" | sort)
|
||||
if [[ "${#package_tgzs[@]}" -ne 1 ]]; then
|
||||
echo "package artifact ${PACKAGE_ARTIFACT_NAME} must contain exactly one .tgz; found ${#package_tgzs[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgzs[0]}"
|
||||
if [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
|
||||
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$(basename "${package_tgzs[0]}")"
|
||||
fi
|
||||
elif [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
|
||||
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}"
|
||||
fi
|
||||
|
||||
if [[ -n "${INPUT_SCENARIO// }" ]]; then
|
||||
export OPENCLAW_NPM_TELEGRAM_SCENARIOS="${INPUT_SCENARIO}"
|
||||
fi
|
||||
@@ -225,6 +205,6 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: npm-telegram-beta-e2e-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: .artifacts/qa-e2e/
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
@@ -51,31 +51,6 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_name:
|
||||
description: Optional current-run artifact name containing the candidate OpenClaw tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_run_id:
|
||||
description: Optional workflow run id for candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_file_name:
|
||||
description: Optional candidate tarball file name inside candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_version:
|
||||
description: Optional candidate OpenClaw package version
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_source_sha:
|
||||
description: Optional source SHA used to build the candidate tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
@@ -115,31 +90,6 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_name:
|
||||
description: Optional current-run artifact name containing the candidate OpenClaw tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_artifact_run_id:
|
||||
description: Optional workflow run id for candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_file_name:
|
||||
description: Optional candidate tarball file name inside candidate_artifact_name
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_version:
|
||||
description: Optional candidate OpenClaw package version
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
candidate_source_sha:
|
||||
description: Optional source SHA used to build the candidate tarball
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
@@ -158,7 +108,7 @@ permissions: read-all
|
||||
|
||||
concurrency:
|
||||
group: openclaw-cross-os-release-checks-${{ inputs.ref }}-${{ inputs.provider }}-${{ inputs.mode }}
|
||||
cancel-in-progress: ${{ inputs.ref == 'main' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -166,11 +116,10 @@ env:
|
||||
PNPM_VERSION: "10.32.1"
|
||||
OPENCLAW_REPOSITORY: openclaw/openclaw
|
||||
TSX_VERSION: "4.21.0"
|
||||
OPENCLAW_CROSS_OS_OPENAI_MODEL: ${{ vars.OPENCLAW_CROSS_OS_OPENAI_MODEL || 'openai/gpt-5.4-mini' }}
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
baseline_file_name: ${{ steps.baseline_metadata.outputs.file_name }}
|
||||
baseline_spec: ${{ steps.baseline.outputs.value }}
|
||||
@@ -311,7 +260,6 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout public source ref
|
||||
if: inputs.candidate_artifact_name == ''
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.OPENCLAW_REPOSITORY }}
|
||||
@@ -322,7 +270,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
@@ -332,74 +280,17 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: pnpm
|
||||
cache-dependency-path: ${{ inputs.candidate_artifact_name == '' && 'source/pnpm-lock.yaml' || 'workflow/pnpm-lock.yaml' }}
|
||||
|
||||
- name: Ensure pnpm store cache directory exists
|
||||
run: mkdir -p "$(pnpm store path --silent)"
|
||||
cache-dependency-path: source/pnpm-lock.yaml
|
||||
|
||||
- name: Build candidate artifact once
|
||||
if: inputs.candidate_artifact_name == ''
|
||||
env:
|
||||
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare
|
||||
run: |
|
||||
bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \
|
||||
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
|
||||
--prepare-only \
|
||||
--source-dir source \
|
||||
--output-dir "${OUTPUT_DIR}"
|
||||
|
||||
- name: Download current-run candidate artifact
|
||||
if: inputs.candidate_artifact_name != '' && inputs.candidate_artifact_run_id == ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.candidate_artifact_name }}
|
||||
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
||||
|
||||
- name: Download previous-run candidate artifact
|
||||
if: inputs.candidate_artifact_name != '' && inputs.candidate_artifact_run_id != ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.candidate_artifact_name }}
|
||||
run-id: ${{ inputs.candidate_artifact_run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
path: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
||||
|
||||
- name: Capture provided candidate artifact metadata
|
||||
if: inputs.candidate_artifact_name != ''
|
||||
env:
|
||||
PACKAGE_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/package
|
||||
INPUT_CANDIDATE_FILE_NAME: ${{ inputs.candidate_file_name }}
|
||||
INPUT_CANDIDATE_VERSION: ${{ inputs.candidate_version }}
|
||||
INPUT_CANDIDATE_SOURCE_SHA: ${{ inputs.candidate_source_sha }}
|
||||
CANDIDATE_JSON: ${{ runner.temp }}/openclaw-cross-os-release-checks/prepare/candidate.json
|
||||
run: |
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const packageDir = process.env.PACKAGE_DIR;
|
||||
const requestedFileName = process.env.INPUT_CANDIDATE_FILE_NAME.trim();
|
||||
const files = fs.readdirSync(packageDir).filter((file) => file.endsWith(".tgz"));
|
||||
const candidateFileName = requestedFileName || (files.length === 1 ? files[0] : "");
|
||||
if (!candidateFileName) {
|
||||
throw new Error(`Expected exactly one candidate .tgz in ${packageDir}; found ${files.length}.`);
|
||||
}
|
||||
if (!fs.existsSync(path.join(packageDir, candidateFileName))) {
|
||||
throw new Error(`Provided candidate artifact does not contain ${candidateFileName}.`);
|
||||
}
|
||||
const candidateVersion = process.env.INPUT_CANDIDATE_VERSION.trim();
|
||||
if (!candidateVersion) {
|
||||
throw new Error("candidate_version is required when candidate_artifact_name is provided.");
|
||||
}
|
||||
const sourceSha = process.env.INPUT_CANDIDATE_SOURCE_SHA.trim();
|
||||
if (!/^[0-9a-f]{40}$/iu.test(sourceSha)) {
|
||||
throw new Error("candidate_source_sha must be a full commit SHA when candidate_artifact_name is provided.");
|
||||
}
|
||||
fs.writeFileSync(
|
||||
process.env.CANDIDATE_JSON,
|
||||
`${JSON.stringify({ candidateFileName, candidateVersion, sourceSha }, null, 2)}\n`,
|
||||
);
|
||||
NODE
|
||||
|
||||
- name: Resolve baseline package spec
|
||||
if: ${{ inputs.mode != 'fresh' }}
|
||||
id: baseline
|
||||
@@ -479,7 +370,7 @@ jobs:
|
||||
VAR_WINDOWS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_WINDOWS_RUNNER }}
|
||||
VAR_MACOS_RUNNER: ${{ vars.OPENCLAW_RELEASE_CHECKS_MACOS_RUNNER }}
|
||||
run: |
|
||||
MATRIX_JSON="$(bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \
|
||||
MATRIX_JSON="$(pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
|
||||
--resolve-matrix \
|
||||
--ref "${INPUT_REF}" \
|
||||
--mode "${INPUT_MODE}" \
|
||||
@@ -507,7 +398,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ env.PNPM_VERSION }}
|
||||
run_install: false
|
||||
@@ -557,7 +448,7 @@ jobs:
|
||||
if [[ -n "${OPENCLAW_DISCORD_SMOKE_BOT_TOKEN}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_GUILD_ID}" ]] && [[ -n "${OPENCLAW_DISCORD_SMOKE_CHANNEL_ID}" ]]; then
|
||||
DISCORD_ARGS+=(--run-discord-roundtrip true)
|
||||
fi
|
||||
bash workflow/scripts/github/run-openclaw-cross-os-release-checks.sh \
|
||||
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
|
||||
--candidate-tgz "${CANDIDATE_TGZ}" \
|
||||
--candidate-version "${CANDIDATE_VERSION}" \
|
||||
--source-sha "${SOURCE_SHA}" \
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
673
.github/workflows/openclaw-release-checks.yml
vendored
673
.github/workflows/openclaw-release-checks.yml
vendored
@@ -4,14 +4,9 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Branch, tag, or full commit SHA to validate
|
||||
description: Existing release tag or current full 40-character workflow-branch commit SHA to validate (for example v2026.4.12 or 0123456789abcdef0123456789abcdef01234567)
|
||||
required: true
|
||||
type: string
|
||||
expected_sha:
|
||||
description: Optional full SHA that ref must resolve to
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
provider:
|
||||
description: Provider lane for cross-OS onboarding and the end-to-end agent turn
|
||||
required: false
|
||||
@@ -30,59 +25,28 @@ on:
|
||||
- fresh
|
||||
- upgrade
|
||||
- both
|
||||
release_profile:
|
||||
description: Release coverage profile for live/Docker/provider breadth
|
||||
required: false
|
||||
default: full
|
||||
type: choice
|
||||
options:
|
||||
- minimum
|
||||
- stable
|
||||
- full
|
||||
rerun_group:
|
||||
description: Release check group to run
|
||||
required: false
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- install-smoke
|
||||
- cross-os
|
||||
- live-e2e
|
||||
- package
|
||||
- qa
|
||||
- qa-parity
|
||||
- qa-live
|
||||
live_suite_filter:
|
||||
description: Optional exact live suite id for focused live/E2E reruns; blank runs all selected live suites
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: openclaw-release-checks-${{ inputs.expected_sha || inputs.ref }}-${{ inputs.rerun_group }}
|
||||
group: openclaw-release-checks-${{ inputs.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.33.0"
|
||||
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL || 'openai/gpt-5.5' }}
|
||||
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL }}
|
||||
|
||||
jobs:
|
||||
resolve_target:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
ref: ${{ steps.inputs.outputs.ref }}
|
||||
revision: ${{ steps.ref.outputs.sha }}
|
||||
sha: ${{ steps.ref.outputs.sha }}
|
||||
provider: ${{ steps.inputs.outputs.provider }}
|
||||
mode: ${{ steps.inputs.outputs.mode }}
|
||||
release_profile: ${{ steps.inputs.outputs.release_profile }}
|
||||
rerun_group: ${{ steps.inputs.outputs.rerun_group }}
|
||||
live_suite_filter: ${{ steps.inputs.outputs.live_suite_filter }}
|
||||
steps:
|
||||
- name: Require main or release workflow ref for release checks
|
||||
env:
|
||||
@@ -97,240 +61,89 @@ jobs:
|
||||
- name: Validate ref input
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${RELEASE_REF// }" ]] || [[ "${RELEASE_REF}" == -* ]]; then
|
||||
echo "Expected a branch, tag, or full commit SHA; got: ${RELEASE_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${EXPECTED_SHA// }" ]] && [[ ! "${EXPECTED_SHA}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "Expected expected_sha to be a full commit SHA; got: ${EXPECTED_SHA}" >&2
|
||||
if [[ ! "${RELEASE_REF}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]] && [[ ! "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "Expected an existing release tag or current full 40-character workflow-branch commit SHA, got: ${RELEASE_REF}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout trusted workflow helper
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.ref_name }}
|
||||
path: workflow
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Fast-resolve selected ref
|
||||
id: fast_ref
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
run: |
|
||||
bash workflow/scripts/github/resolve-openclaw-ref.sh \
|
||||
--ref "$RELEASE_REF" \
|
||||
--expected-sha "$EXPECTED_SHA" \
|
||||
--fallback-ok \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout selected ref for reachability fallback
|
||||
if: steps.fast_ref.outputs.fallback == 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ inputs.ref }}
|
||||
path: source
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve checked-out fallback SHA
|
||||
if: steps.fast_ref.outputs.fallback == 'true'
|
||||
id: fallback_ref
|
||||
working-directory: source
|
||||
- name: Resolve checked-out SHA
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate selected ref belongs to this repository
|
||||
if: steps.fast_ref.outputs.fallback == 'true'
|
||||
working-directory: source
|
||||
- name: Validate selected ref is on workflow branch
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SELECTED_SHA="$(git rev-parse HEAD)"
|
||||
git fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
|
||||
git fetch --tags origin '+refs/tags/*:refs/tags/*'
|
||||
|
||||
if git tag --points-at "${SELECTED_SHA}" | grep -Eq '^v'; then
|
||||
exit 0
|
||||
RELEASE_BRANCH_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
if [[ "${RELEASE_REF}" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
BRANCH_SHA="$(git rev-parse "${RELEASE_BRANCH_REF}")"
|
||||
if [[ "$(git rev-parse HEAD)" != "${BRANCH_SHA}" ]]; then
|
||||
echo "Commit SHA mode only supports the current ${WORKFLOW_REF_NAME} HEAD. Use a release tag for older commits." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
git merge-base --is-ancestor HEAD "${RELEASE_BRANCH_REF}"
|
||||
fi
|
||||
|
||||
if git for-each-ref --format='%(refname:short)' --contains "${SELECTED_SHA}" refs/remotes/origin | grep -Eq '^origin/'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Ref '${RELEASE_REF}' resolved to ${SELECTED_SHA}, but that commit is not reachable from an OpenClaw branch or release tag." >&2
|
||||
echo "Secret-bearing release checks only run repository-owned branch/tag history, not arbitrary unreferenced commits." >&2
|
||||
exit 1
|
||||
|
||||
- name: Finalize resolved SHA
|
||||
id: ref
|
||||
env:
|
||||
FAST_SHA: ${{ steps.fast_ref.outputs.sha }}
|
||||
FALLBACK_SHA: ${{ steps.fallback_ref.outputs.sha }}
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
USED_FALLBACK: ${{ steps.fast_ref.outputs.fallback }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
selected_sha="$FAST_SHA"
|
||||
if [[ "$USED_FALLBACK" == "true" ]]; then
|
||||
selected_sha="$FALLBACK_SHA"
|
||||
fi
|
||||
if [[ -z "$selected_sha" ]]; then
|
||||
echo "Failed to resolve selected ref SHA." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${EXPECTED_SHA// }" ]] && [[ "${selected_sha,,}" != "${EXPECTED_SHA,,}" ]]; then
|
||||
echo "Ref resolved to ${selected_sha}, expected ${EXPECTED_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "sha=${selected_sha,,}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Capture selected inputs
|
||||
id: inputs
|
||||
env:
|
||||
RELEASE_REF_INPUT: ${{ inputs.ref }}
|
||||
RELEASE_PROVIDER_INPUT: ${{ inputs.provider }}
|
||||
RELEASE_MODE_INPUT: ${{ inputs.mode }}
|
||||
RELEASE_PROFILE_INPUT: ${{ inputs.release_profile }}
|
||||
RELEASE_RERUN_GROUP_INPUT: ${{ inputs.rerun_group }}
|
||||
RELEASE_LIVE_SUITE_FILTER_INPUT: ${{ inputs.live_suite_filter }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
{
|
||||
printf 'ref=%s\n' "$RELEASE_REF_INPUT"
|
||||
printf 'provider=%s\n' "$RELEASE_PROVIDER_INPUT"
|
||||
printf 'mode=%s\n' "$RELEASE_MODE_INPUT"
|
||||
printf 'release_profile=%s\n' "$RELEASE_PROFILE_INPUT"
|
||||
printf 'rerun_group=%s\n' "$RELEASE_RERUN_GROUP_INPUT"
|
||||
printf 'live_suite_filter=%s\n' "$RELEASE_LIVE_SUITE_FILTER_INPUT"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Summarize validated ref
|
||||
env:
|
||||
RELEASE_REF: ${{ inputs.ref }}
|
||||
RELEASE_SHA: ${{ steps.ref.outputs.sha }}
|
||||
RELEASE_REF_FAST_PATH: ${{ steps.fast_ref.outputs.fast }}
|
||||
RELEASE_PROVIDER: ${{ inputs.provider }}
|
||||
RELEASE_MODE: ${{ inputs.mode }}
|
||||
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
||||
RELEASE_RERUN_GROUP: ${{ inputs.rerun_group }}
|
||||
RELEASE_LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
|
||||
run: |
|
||||
{
|
||||
echo "## Release checks"
|
||||
echo
|
||||
echo "- Requested ref: \`${RELEASE_REF}\`"
|
||||
echo "- Validated SHA: \`${RELEASE_SHA}\`"
|
||||
echo "- Ref resolution fast path: \`${RELEASE_REF_FAST_PATH}\`"
|
||||
echo "- Cross-OS provider: \`${RELEASE_PROVIDER}\`"
|
||||
echo "- Cross-OS mode: \`${RELEASE_MODE}\`"
|
||||
echo "- Release profile: \`${RELEASE_PROFILE}\`"
|
||||
echo "- Rerun group: \`${RELEASE_RERUN_GROUP}\`"
|
||||
if [[ -n "${RELEASE_LIVE_SUITE_FILTER// }" ]]; then
|
||||
echo "- Live suite filter: \`${RELEASE_LIVE_SUITE_FILTER}\`"
|
||||
fi
|
||||
echo "- This run will execute cross-OS release validation, install smoke, QA Lab parity, Matrix, and Telegram lanes, and the non-Parallels Docker/live/openwebui coverage from the CI migration plan."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
prepare_release_package:
|
||||
name: Prepare release package artifact
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","cross-os","package"]'), needs.resolve_target.outputs.rerun_group) || (needs.resolve_target.outputs.rerun_group == 'live-e2e' && needs.resolve_target.outputs.live_suite_filter == '')
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
artifact_name: ${{ steps.artifact.outputs.name }}
|
||||
package_sha256: ${{ steps.package.outputs.sha256 }}
|
||||
package_version: ${{ steps.package.outputs.package_version }}
|
||||
source_sha: ${{ steps.package.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout trusted workflow ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.ref_name }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set artifact metadata
|
||||
id: artifact
|
||||
run: echo "name=release-package-under-test" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
install-deps: "false"
|
||||
|
||||
- name: Resolve release package artifact
|
||||
id: package
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_REF: ${{ needs.resolve_target.outputs.revision }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node scripts/resolve-openclaw-package-candidate.mjs \
|
||||
--source ref \
|
||||
--package-ref "$PACKAGE_REF" \
|
||||
--output-dir .artifacts/docker-e2e-package \
|
||||
--output-name openclaw-current.tgz \
|
||||
--metadata .artifacts/docker-e2e-package/package-candidate.json \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
digest="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).sha256")"
|
||||
version="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).version")"
|
||||
source_sha="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).packageSourceSha")"
|
||||
echo "source_sha=$source_sha" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Release package artifact"
|
||||
echo
|
||||
echo "- Artifact: \`release-package-under-test\`"
|
||||
echo "- Package ref: \`$PACKAGE_REF\`"
|
||||
echo "- SHA-256: \`$digest\`"
|
||||
echo "- Version: \`$version\`"
|
||||
echo "- Source SHA: \`$source_sha\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload release package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: release-package-under-test
|
||||
path: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
install_smoke_release_checks:
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","install-smoke"]'), needs.resolve_target.outputs.rerun_group)
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
uses: ./.github/workflows/install-smoke.yml
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
run_bun_global_install_smoke: true
|
||||
|
||||
cross_os_release_checks:
|
||||
needs: [resolve_target, prepare_release_package]
|
||||
if: contains(fromJSON('["all","cross-os"]'), needs.resolve_target.outputs.rerun_group)
|
||||
needs: [resolve_target]
|
||||
permissions: read-all
|
||||
uses: ./.github/workflows/openclaw-cross-os-release-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
provider: ${{ needs.resolve_target.outputs.provider }}
|
||||
mode: ${{ needs.resolve_target.outputs.mode }}
|
||||
candidate_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
|
||||
candidate_file_name: openclaw-current.tgz
|
||||
candidate_version: ${{ needs.prepare_release_package.outputs.package_version }}
|
||||
candidate_source_sha: ${{ needs.prepare_release_package.outputs.source_sha }}
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
@@ -339,110 +152,19 @@ jobs:
|
||||
OPENCLAW_DISCORD_SMOKE_GUILD_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_GUILD_ID }}
|
||||
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
|
||||
|
||||
live_repo_e2e_release_checks:
|
||||
name: Run repo/live E2E validation
|
||||
live_and_e2e_release_checks:
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","live-e2e"]'), needs.resolve_target.outputs.rerun_group)
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
include_repo_e2e: true
|
||||
include_release_path_suites: false
|
||||
include_openwebui: false
|
||||
include_live_suites: true
|
||||
release_test_profile: ${{ needs.resolve_target.outputs.release_profile }}
|
||||
live_suite_filter: ${{ needs.resolve_target.outputs.live_suite_filter }}
|
||||
secrets: &live_e2e_release_secrets
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
||||
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
FAL_KEY: ${{ secrets.FAL_KEY }}
|
||||
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
||||
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
||||
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
||||
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
||||
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
||||
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
||||
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
||||
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
|
||||
docker_e2e_release_checks:
|
||||
name: Run Docker release-path validation
|
||||
needs: [resolve_target, prepare_release_package]
|
||||
if: contains(fromJSON('["all","live-e2e"]'), needs.resolve_target.outputs.rerun_group) && needs.resolve_target.outputs.live_suite_filter == ''
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
include_repo_e2e: false
|
||||
include_release_path_suites: true
|
||||
include_openwebui: ${{ needs.resolve_target.outputs.release_profile != 'minimum' }}
|
||||
include_live_suites: false
|
||||
release_test_profile: ${{ needs.resolve_target.outputs.release_profile }}
|
||||
package_artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
|
||||
secrets: *live_e2e_release_secrets
|
||||
|
||||
package_acceptance_release_checks:
|
||||
name: Run package acceptance
|
||||
needs: [resolve_target, prepare_release_package]
|
||||
if: contains(fromJSON('["all","package"]'), needs.resolve_target.outputs.rerun_group)
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/package-acceptance.yml
|
||||
with:
|
||||
workflow_ref: ${{ github.ref_name }}
|
||||
source: artifact
|
||||
artifact_name: ${{ needs.prepare_release_package.outputs.artifact_name }}
|
||||
package_sha256: ${{ needs.prepare_release_package.outputs.package_sha256 }}
|
||||
suite_profile: custom
|
||||
docker_lanes: bundled-channel-deps-compat plugins-offline
|
||||
telegram_mode: mock-openai
|
||||
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-context-command,telegram-mention-gating
|
||||
include_openwebui: true
|
||||
include_live_suites: true
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
@@ -451,7 +173,6 @@ jobs:
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
@@ -489,25 +210,14 @@ jobs:
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
|
||||
qa_lab_parity_lane_release_checks:
|
||||
name: Run QA Lab parity lane (${{ matrix.lane }})
|
||||
qa_lab_parity_release_checks:
|
||||
name: Run QA Lab parity gate
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- lane: candidate
|
||||
output_dir: gpt54
|
||||
- lane: baseline
|
||||
output_dir: opus46
|
||||
env:
|
||||
QA_PARITY_CONCURRENCY: "1"
|
||||
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
|
||||
@@ -523,8 +233,7 @@ jobs:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -537,81 +246,25 @@ jobs:
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run parity lane
|
||||
env:
|
||||
QA_PARITY_LANE: ${{ matrix.lane }}
|
||||
QA_PARITY_OUTPUT_DIR: ${{ matrix.output_dir }}
|
||||
- name: Run OpenAI candidate lane
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
case "${QA_PARITY_LANE}" in
|
||||
candidate)
|
||||
model="${OPENCLAW_CI_OPENAI_MODEL}"
|
||||
alt_model="openai/gpt-5.4-alt"
|
||||
;;
|
||||
baseline)
|
||||
model="anthropic/claude-opus-4-6"
|
||||
alt_model="anthropic/claude-sonnet-4-6"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown QA parity lane: ${QA_PARITY_LANE}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
pnpm openclaw qa suite \
|
||||
--provider-mode mock-openai \
|
||||
--parity-pack agentic \
|
||||
--concurrency "${QA_PARITY_CONCURRENCY}" \
|
||||
--model "${model}" \
|
||||
--alt-model "${alt_model}" \
|
||||
--output-dir ".artifacts/qa-e2e/${QA_PARITY_OUTPUT_DIR}"
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model openai/gpt-5.4-alt \
|
||||
--output-dir .artifacts/qa-e2e/gpt54
|
||||
|
||||
- name: Upload parity lane artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-qa-parity-${{ matrix.lane }}-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
qa_lab_parity_report_release_checks:
|
||||
name: Run QA Lab parity report
|
||||
needs: [resolve_target, qa_lab_parity_lane_release_checks]
|
||||
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
env:
|
||||
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
||||
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Download parity lane artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: release-qa-parity-*-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
- name: Run Opus 4.6 lane
|
||||
run: |
|
||||
pnpm openclaw qa suite \
|
||||
--provider-mode mock-openai \
|
||||
--parity-pack agentic \
|
||||
--concurrency "${QA_PARITY_CONCURRENCY}" \
|
||||
--model anthropic/claude-opus-4-6 \
|
||||
--alt-model anthropic/claude-sonnet-4-6 \
|
||||
--output-dir .artifacts/qa-e2e/opus46
|
||||
|
||||
- name: Generate parity report
|
||||
run: |
|
||||
@@ -627,7 +280,7 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-qa-parity-${{ needs.resolve_target.outputs.revision }}
|
||||
name: release-qa-parity-${{ needs.resolve_target.outputs.sha }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
@@ -635,8 +288,7 @@ jobs:
|
||||
qa_live_matrix_release_checks:
|
||||
name: Run QA Lab live Matrix lane
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group)
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -649,85 +301,7 @@ jobs:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Matrix live lane
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_MATRIX_CANARY_TIMEOUT_MS: "90000"
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
output_dir=".artifacts/qa-e2e/matrix-live-release-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
matrix_args=(
|
||||
--repo-root . \
|
||||
--provider-mode mock-openai \
|
||||
--model mock-openai/gpt-5.5 \
|
||||
--alt-model mock-openai/gpt-5.5-alt \
|
||||
--profile fast \
|
||||
--fast
|
||||
)
|
||||
if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then
|
||||
matrix_args+=(--fail-fast)
|
||||
fi
|
||||
|
||||
for attempt in 1 2; do
|
||||
attempt_output_dir="${output_dir}/attempt-${attempt}"
|
||||
if pnpm openclaw qa matrix --output-dir "${attempt_output_dir}" "${matrix_args[@]}"; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${attempt}" == "2" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Matrix live lane failed on attempt ${attempt}; retrying once..." >&2
|
||||
sleep 10
|
||||
done
|
||||
|
||||
- name: Upload Matrix QA artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-qa-live-matrix-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
qa_live_telegram_release_checks:
|
||||
name: Run QA Lab live Telegram lane
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","qa","qa-live"]'), needs.resolve_target.outputs.rerun_group)
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
environment: qa-live-shared
|
||||
env:
|
||||
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
||||
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.resolve_target.outputs.revision }}
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -739,6 +313,77 @@ jobs:
|
||||
|
||||
- name: Validate required QA credential env
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
||||
echo "Missing required OPENAI_API_KEY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Matrix live lane
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
output_dir=".artifacts/qa-e2e/matrix-live-release-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
pnpm openclaw qa matrix \
|
||||
--repo-root . \
|
||||
--output-dir "${output_dir}" \
|
||||
--provider-mode live-frontier \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--fast
|
||||
|
||||
- name: Upload Matrix QA artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-qa-live-matrix-${{ needs.resolve_target.outputs.sha }}
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
qa_live_telegram_release_checks:
|
||||
name: Run QA Lab live Telegram lane
|
||||
needs: [resolve_target]
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
environment: qa-live-shared
|
||||
env:
|
||||
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
||||
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.resolve_target.outputs.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Validate required QA credential env
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
shell: bash
|
||||
@@ -753,6 +398,7 @@ jobs:
|
||||
fi
|
||||
}
|
||||
|
||||
require_var OPENAI_API_KEY
|
||||
require_var OPENCLAW_QA_CONVEX_SITE_URL
|
||||
require_var OPENCLAW_QA_CONVEX_SECRET_CI
|
||||
|
||||
@@ -763,6 +409,7 @@ jobs:
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
@@ -773,75 +420,21 @@ jobs:
|
||||
output_dir=".artifacts/qa-e2e/telegram-live-release-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
for attempt in 1 2; do
|
||||
attempt_output_dir="${output_dir}/attempt-${attempt}"
|
||||
if pnpm openclaw qa telegram \
|
||||
--repo-root . \
|
||||
--output-dir "${attempt_output_dir}" \
|
||||
--provider-mode mock-openai \
|
||||
--model mock-openai/gpt-5.5 \
|
||||
--alt-model mock-openai/gpt-5.5-alt \
|
||||
--fast \
|
||||
--credential-source convex \
|
||||
--credential-role ci; then
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${attempt}" == "2" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "Telegram live lane failed on attempt ${attempt}; retrying once..." >&2
|
||||
sleep 10
|
||||
done
|
||||
pnpm openclaw qa telegram \
|
||||
--repo-root . \
|
||||
--output-dir "${output_dir}" \
|
||||
--provider-mode live-frontier \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--fast \
|
||||
--credential-source convex \
|
||||
--credential-role ci
|
||||
|
||||
- name: Upload Telegram QA artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-qa-live-telegram-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
name: release-qa-live-telegram-${{ needs.resolve_target.outputs.sha }}
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
summary:
|
||||
name: Verify release checks
|
||||
needs:
|
||||
- prepare_release_package
|
||||
- install_smoke_release_checks
|
||||
- cross_os_release_checks
|
||||
- live_repo_e2e_release_checks
|
||||
- docker_e2e_release_checks
|
||||
- package_acceptance_release_checks
|
||||
- qa_lab_parity_lane_release_checks
|
||||
- qa_lab_parity_report_release_checks
|
||||
- qa_live_matrix_release_checks
|
||||
- qa_live_telegram_release_checks
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
permissions: {}
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify release check results
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
for item in \
|
||||
"prepare_release_package=${{ needs.prepare_release_package.result }}" \
|
||||
"install_smoke_release_checks=${{ needs.install_smoke_release_checks.result }}" \
|
||||
"cross_os_release_checks=${{ needs.cross_os_release_checks.result }}" \
|
||||
"live_repo_e2e_release_checks=${{ needs.live_repo_e2e_release_checks.result }}" \
|
||||
"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 }}" \
|
||||
"qa_lab_parity_report_release_checks=${{ needs.qa_lab_parity_report_release_checks.result }}" \
|
||||
"qa_live_matrix_release_checks=${{ needs.qa_live_matrix_release_checks.result }}" \
|
||||
"qa_live_telegram_release_checks=${{ needs.qa_live_telegram_release_checks.result }}"
|
||||
do
|
||||
name="${item%%=*}"
|
||||
result="${item#*=}"
|
||||
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
|
||||
echo "::error::${name} ended with ${result}"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit "$failed"
|
||||
|
||||
@@ -38,7 +38,6 @@ jobs:
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
|
||||
67
.github/workflows/opengrep-precise-full.yml
vendored
67
.github/workflows/opengrep-precise-full.yml
vendored
@@ -1,67 +0,0 @@
|
||||
name: OpenGrep — Full
|
||||
|
||||
# Manual repository-wide scan for the high-precision OpenGrep rule super-config.
|
||||
# This is intentionally separate from PR scanning so broad/backlog findings do
|
||||
# not block unrelated pull requests.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: opengrep-full-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
name: Scan full repository (precise)
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install opengrep
|
||||
env:
|
||||
# Pin both the install script (by commit SHA) and the binary version.
|
||||
# The script SHA must match the v1.19.0 release tag in opengrep/opengrep
|
||||
# so a compromised or force-pushed `main` cannot RCE in our CI runner.
|
||||
# Bump both together when upgrading.
|
||||
OPENGREP_VERSION: v1.19.0
|
||||
OPENGREP_INSTALL_SHA: 9a4c0a68220618441608cd2bad4ff2eddccf8113
|
||||
run: |
|
||||
curl -fsSL "https://raw.githubusercontent.com/opengrep/opengrep/${OPENGREP_INSTALL_SHA}/install.sh" \
|
||||
| bash -s -- -v "$OPENGREP_VERSION"
|
||||
echo "$HOME/.opengrep/cli/latest" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Verify opengrep
|
||||
run: opengrep --version
|
||||
|
||||
- name: Run full opengrep scan
|
||||
# Manual full scans cover all first-party source paths so maintainers can
|
||||
# audit the complete rulepack without making PRs inherit unrelated backlog.
|
||||
run: |
|
||||
mkdir -p .opengrep-out
|
||||
scripts/run-opengrep.sh --sarif --error
|
||||
|
||||
- name: Upload SARIF to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
# Only upload if the scan actually produced a SARIF file.
|
||||
if: always() && hashFiles('.opengrep-out/precise.sarif') != ''
|
||||
with:
|
||||
sarif_file: .opengrep-out/precise.sarif
|
||||
category: opengrep-full
|
||||
|
||||
- name: Upload SARIF as workflow artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: opengrep-full-sarif
|
||||
path: .opengrep-out/precise.sarif
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
76
.github/workflows/opengrep-precise.yml
vendored
76
.github/workflows/opengrep-precise.yml
vendored
@@ -1,76 +0,0 @@
|
||||
name: OpenGrep — PR Diff
|
||||
|
||||
# Runs the high-precision OpenGrep rule super-config against only first-party
|
||||
# source paths changed by a pull request. Keeping PR scans diff-scoped makes
|
||||
# findings attributable to the proposed change instead of surfacing unrelated
|
||||
# repository-wide backlog.
|
||||
#
|
||||
# For a repository-wide scan, use the manual OpenGrep — Full workflow.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: opengrep-pr-diff-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
name: Scan changed paths (precise)
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
# `scripts/run-opengrep.sh --changed` diffs base...HEAD.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install opengrep
|
||||
env:
|
||||
# Pin both the install script (by commit SHA) and the binary version.
|
||||
# The script SHA must match the v1.19.0 release tag in opengrep/opengrep
|
||||
# so a compromised or force-pushed `main` cannot RCE in our CI runner.
|
||||
# Bump both together when upgrading.
|
||||
OPENGREP_VERSION: v1.19.0
|
||||
OPENGREP_INSTALL_SHA: 9a4c0a68220618441608cd2bad4ff2eddccf8113
|
||||
run: |
|
||||
curl -fsSL "https://raw.githubusercontent.com/opengrep/opengrep/${OPENGREP_INSTALL_SHA}/install.sh" \
|
||||
| bash -s -- -v "$OPENGREP_VERSION"
|
||||
echo "$HOME/.opengrep/cli/latest" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Verify opengrep
|
||||
run: opengrep --version
|
||||
|
||||
- name: Run opengrep on PR diff
|
||||
env:
|
||||
OPENCLAW_OPENGREP_BASE_REF: ${{ github.event.pull_request.base.sha }}...HEAD
|
||||
# Findings from precise rules block this workflow. Pull requests scan
|
||||
# changed first-party source paths only so findings stay attributable to
|
||||
# the PR diff. Test/fixture/QA path exclusions live in `.semgrepignore`
|
||||
# at the repo root and are picked up automatically.
|
||||
run: |
|
||||
mkdir -p .opengrep-out
|
||||
scripts/run-opengrep.sh --changed --sarif --error
|
||||
|
||||
- name: Upload SARIF to GitHub Code Scanning
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
# Only upload if the scan actually produced a SARIF file.
|
||||
if: always() && hashFiles('.opengrep-out/precise.sarif') != ''
|
||||
with:
|
||||
sarif_file: .opengrep-out/precise.sarif
|
||||
category: opengrep-pr-diff
|
||||
|
||||
- name: Upload SARIF as workflow artifact
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: opengrep-pr-diff-sarif
|
||||
path: .opengrep-out/precise.sarif
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
536
.github/workflows/package-acceptance.yml
vendored
536
.github/workflows/package-acceptance.yml
vendored
@@ -1,536 +0,0 @@
|
||||
name: Package Acceptance
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
workflow_ref:
|
||||
description: Trusted repo ref for workflow scripts and Docker E2E harness
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
source:
|
||||
description: Package candidate source
|
||||
required: true
|
||||
default: npm
|
||||
type: choice
|
||||
options:
|
||||
- npm
|
||||
- ref
|
||||
- url
|
||||
- artifact
|
||||
package_ref:
|
||||
description: Trusted package source ref when source=ref
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
package_spec:
|
||||
description: Published package spec when source=npm
|
||||
required: false
|
||||
default: openclaw@beta
|
||||
type: string
|
||||
package_url:
|
||||
description: HTTPS .tgz URL when source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_sha256:
|
||||
description: Expected package SHA-256; required for source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_run_id:
|
||||
description: GitHub Actions run id when source=artifact
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_name:
|
||||
description: Artifact name containing one .tgz when source=artifact
|
||||
required: false
|
||||
default: package-under-test
|
||||
type: string
|
||||
suite_profile:
|
||||
description: Acceptance profile
|
||||
required: true
|
||||
default: package
|
||||
type: choice
|
||||
options:
|
||||
- smoke
|
||||
- package
|
||||
- product
|
||||
- full
|
||||
- custom
|
||||
docker_lanes:
|
||||
description: Comma/space separated Docker lanes when suite_profile=custom
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
telegram_mode:
|
||||
description: Optional Telegram QA lane for the resolved package candidate
|
||||
required: true
|
||||
default: none
|
||||
type: choice
|
||||
options:
|
||||
- none
|
||||
- mock-openai
|
||||
- live-frontier
|
||||
telegram_scenarios:
|
||||
description: Optional comma-separated Telegram scenario ids
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
workflow_ref:
|
||||
description: Trusted repo ref for workflow scripts and Docker E2E harness
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
source:
|
||||
description: "Package candidate source: npm, ref, url, or artifact"
|
||||
required: true
|
||||
type: string
|
||||
package_ref:
|
||||
description: Trusted package source ref when source=ref
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
package_spec:
|
||||
description: Published package spec when source=npm
|
||||
required: false
|
||||
default: openclaw@beta
|
||||
type: string
|
||||
package_url:
|
||||
description: HTTPS .tgz URL when source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
package_sha256:
|
||||
description: Expected package SHA-256; required for source=url
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_run_id:
|
||||
description: GitHub Actions run id when source=artifact
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
artifact_name:
|
||||
description: Artifact name containing one .tgz when source=artifact
|
||||
required: false
|
||||
default: package-under-test
|
||||
type: string
|
||||
suite_profile:
|
||||
description: "Acceptance profile: smoke, package, product, full, or custom"
|
||||
required: false
|
||||
default: package
|
||||
type: string
|
||||
docker_lanes:
|
||||
description: Comma/space separated Docker lanes when suite_profile=custom
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
telegram_mode:
|
||||
description: Optional Telegram QA lane for the resolved package candidate
|
||||
required: false
|
||||
default: none
|
||||
type: string
|
||||
telegram_scenarios:
|
||||
description: Optional comma-separated Telegram scenario ids
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
secrets:
|
||||
OPENAI_API_KEY:
|
||||
required: false
|
||||
OPENAI_BASE_URL:
|
||||
required: false
|
||||
ANTHROPIC_API_KEY:
|
||||
required: false
|
||||
ANTHROPIC_API_KEY_OLD:
|
||||
required: false
|
||||
ANTHROPIC_API_TOKEN:
|
||||
required: false
|
||||
BYTEPLUS_API_KEY:
|
||||
required: false
|
||||
CEREBRAS_API_KEY:
|
||||
required: false
|
||||
DEEPINFRA_API_KEY:
|
||||
required: false
|
||||
DASHSCOPE_API_KEY:
|
||||
required: false
|
||||
GROQ_API_KEY:
|
||||
required: false
|
||||
KIMI_API_KEY:
|
||||
required: false
|
||||
MODELSTUDIO_API_KEY:
|
||||
required: false
|
||||
MOONSHOT_API_KEY:
|
||||
required: false
|
||||
MISTRAL_API_KEY:
|
||||
required: false
|
||||
MINIMAX_API_KEY:
|
||||
required: false
|
||||
OPENCODE_API_KEY:
|
||||
required: false
|
||||
OPENCODE_ZEN_API_KEY:
|
||||
required: false
|
||||
OPENCLAW_LIVE_BROWSER_CDP_URL:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_MODEL:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE:
|
||||
required: false
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE:
|
||||
required: false
|
||||
GEMINI_API_KEY:
|
||||
required: false
|
||||
GOOGLE_API_KEY:
|
||||
required: false
|
||||
OPENROUTER_API_KEY:
|
||||
required: false
|
||||
QWEN_API_KEY:
|
||||
required: false
|
||||
FAL_KEY:
|
||||
required: false
|
||||
RUNWAY_API_KEY:
|
||||
required: false
|
||||
DEEPGRAM_API_KEY:
|
||||
required: false
|
||||
TOGETHER_API_KEY:
|
||||
required: false
|
||||
VYDRA_API_KEY:
|
||||
required: false
|
||||
XAI_API_KEY:
|
||||
required: false
|
||||
ZAI_API_KEY:
|
||||
required: false
|
||||
Z_AI_API_KEY:
|
||||
required: false
|
||||
BYTEPLUS_ACCESS_KEY_ID:
|
||||
required: false
|
||||
BYTEPLUS_SECRET_ACCESS_KEY:
|
||||
required: false
|
||||
CLAUDE_CODE_OAUTH_TOKEN:
|
||||
required: false
|
||||
OPENCLAW_CODEX_AUTH_JSON:
|
||||
required: false
|
||||
OPENCLAW_CODEX_CONFIG_TOML:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_JSON:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_CREDENTIALS_JSON:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_SETTINGS_JSON:
|
||||
required: false
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON:
|
||||
required: false
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON:
|
||||
required: false
|
||||
FIREWORKS_API_KEY:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SITE_URL:
|
||||
required: false
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: package-acceptance-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.33.0"
|
||||
PACKAGE_ARTIFACT_NAME: package-under-test
|
||||
|
||||
jobs:
|
||||
resolve_package:
|
||||
name: Resolve package candidate
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
outputs:
|
||||
docker_lanes: ${{ steps.profile.outputs.docker_lanes }}
|
||||
include_live_suites: ${{ steps.profile.outputs.include_live_suites }}
|
||||
include_openwebui: ${{ steps.profile.outputs.include_openwebui }}
|
||||
include_release_path_suites: ${{ steps.profile.outputs.include_release_path_suites }}
|
||||
package_artifact_name: ${{ steps.profile.outputs.package_artifact_name }}
|
||||
package_source_sha: ${{ steps.resolve.outputs.package_source_sha }}
|
||||
package_sha256: ${{ steps.resolve.outputs.sha256 }}
|
||||
package_version: ${{ steps.resolve.outputs.package_version }}
|
||||
telegram_enabled: ${{ steps.profile.outputs.telegram_enabled }}
|
||||
telegram_mode: ${{ steps.profile.outputs.telegram_mode }}
|
||||
steps:
|
||||
- name: Checkout package workflow ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.workflow_ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: ${{ inputs.source == 'ref' && 'true' || 'false' }}
|
||||
install-deps: "false"
|
||||
|
||||
- name: Download current-run package artifact input
|
||||
if: inputs.source == 'artifact' && inputs.artifact_run_id == ''
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: .artifacts/package-candidate-input
|
||||
|
||||
- name: Download previous-run package artifact input
|
||||
if: inputs.source == 'artifact' && inputs.artifact_run_id != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
|
||||
ARTIFACT_NAME: ${{ inputs.artifact_name }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${ARTIFACT_NAME// }" ]]; then
|
||||
echo "artifact_name is required when source=artifact." >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p .artifacts/package-candidate-input
|
||||
gh run download "$ARTIFACT_RUN_ID" -n "$ARTIFACT_NAME" -D .artifacts/package-candidate-input
|
||||
|
||||
- name: Resolve package candidate
|
||||
id: resolve
|
||||
env:
|
||||
SOURCE: ${{ inputs.source }}
|
||||
PACKAGE_REF: ${{ inputs.package_ref }}
|
||||
PACKAGE_SPEC: ${{ inputs.package_spec }}
|
||||
PACKAGE_URL: ${{ inputs.package_url }}
|
||||
PACKAGE_SHA256: ${{ inputs.package_sha256 }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
artifact_dir=""
|
||||
if [[ "$SOURCE" == "artifact" ]]; then
|
||||
artifact_dir=".artifacts/package-candidate-input"
|
||||
fi
|
||||
|
||||
node scripts/resolve-openclaw-package-candidate.mjs \
|
||||
--source "$SOURCE" \
|
||||
--package-ref "$PACKAGE_REF" \
|
||||
--package-spec "$PACKAGE_SPEC" \
|
||||
--package-url "$PACKAGE_URL" \
|
||||
--package-sha256 "$PACKAGE_SHA256" \
|
||||
--artifact-dir "${artifact_dir:-.}" \
|
||||
--output-dir .artifacts/docker-e2e-package \
|
||||
--output-name openclaw-current.tgz \
|
||||
--metadata .artifacts/docker-e2e-package/package-candidate.json \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Select acceptance profile
|
||||
id: profile
|
||||
env:
|
||||
SOURCE: ${{ inputs.source }}
|
||||
SUITE_PROFILE: ${{ inputs.suite_profile }}
|
||||
CUSTOM_DOCKER_LANES: ${{ inputs.docker_lanes }}
|
||||
TELEGRAM_MODE: ${{ inputs.telegram_mode }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
include_release_path_suites=false
|
||||
include_openwebui=false
|
||||
include_live_suites=false
|
||||
docker_lanes=""
|
||||
|
||||
case "$SUITE_PROFILE" in
|
||||
smoke)
|
||||
docker_lanes="npm-onboard-channel-agent gateway-network config-reload"
|
||||
;;
|
||||
package)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch bundled-channel-deps-compat plugins-offline plugin-update"
|
||||
;;
|
||||
product)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch bundled-channel-deps-compat plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui"
|
||||
include_openwebui=true
|
||||
;;
|
||||
full)
|
||||
include_release_path_suites=true
|
||||
include_openwebui=true
|
||||
;;
|
||||
custom)
|
||||
docker_lanes="$CUSTOM_DOCKER_LANES"
|
||||
if [[ -z "${docker_lanes// }" ]]; then
|
||||
echo "docker_lanes is required when suite_profile=custom." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$docker_lanes" == *"openwebui"* ]]; then
|
||||
include_openwebui=true
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Unknown suite_profile: $SUITE_PROFILE" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
telegram_enabled=false
|
||||
if [[ "$TELEGRAM_MODE" != "none" ]]; then
|
||||
telegram_enabled=true
|
||||
fi
|
||||
|
||||
{
|
||||
echo "docker_lanes=$docker_lanes"
|
||||
echo "include_release_path_suites=$include_release_path_suites"
|
||||
echo "include_openwebui=$include_openwebui"
|
||||
echo "include_live_suites=$include_live_suites"
|
||||
echo "telegram_enabled=$telegram_enabled"
|
||||
echo "telegram_mode=$TELEGRAM_MODE"
|
||||
echo "package_artifact_name=${PACKAGE_ARTIFACT_NAME}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload package-under-test artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ env.PACKAGE_ARTIFACT_NAME }}
|
||||
path: |
|
||||
.artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
.artifacts/docker-e2e-package/package-candidate.json
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Summarize package candidate
|
||||
env:
|
||||
PACKAGE_SHA256: ${{ steps.resolve.outputs.sha256 }}
|
||||
PACKAGE_VERSION: ${{ steps.resolve.outputs.package_version }}
|
||||
PACKAGE_REF: ${{ inputs.package_ref }}
|
||||
SOURCE: ${{ inputs.source }}
|
||||
SUITE_PROFILE: ${{ inputs.suite_profile }}
|
||||
WORKFLOW_REF: ${{ inputs.workflow_ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
{
|
||||
echo "## Package acceptance"
|
||||
echo
|
||||
echo "- Source: \`${SOURCE}\`"
|
||||
echo "- Workflow ref: \`${WORKFLOW_REF}\`"
|
||||
if [[ "${SOURCE}" == "ref" ]]; then
|
||||
echo "- Package ref: \`${PACKAGE_REF}\`"
|
||||
fi
|
||||
echo "- Version: \`${PACKAGE_VERSION}\`"
|
||||
echo "- SHA-256: \`${PACKAGE_SHA256}\`"
|
||||
echo "- Profile: \`${SUITE_PROFILE}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
docker_acceptance:
|
||||
name: Docker product acceptance
|
||||
needs: resolve_package
|
||||
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ inputs.workflow_ref }}
|
||||
include_repo_e2e: false
|
||||
include_release_path_suites: ${{ needs.resolve_package.outputs.include_release_path_suites == 'true' }}
|
||||
include_openwebui: ${{ needs.resolve_package.outputs.include_openwebui == 'true' }}
|
||||
docker_lanes: ${{ needs.resolve_package.outputs.docker_lanes }}
|
||||
package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}
|
||||
include_live_suites: ${{ needs.resolve_package.outputs.include_live_suites == 'true' }}
|
||||
live_models_only: false
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
|
||||
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
|
||||
BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }}
|
||||
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
|
||||
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
|
||||
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
|
||||
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
|
||||
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
|
||||
MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }}
|
||||
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
|
||||
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
|
||||
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }}
|
||||
OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }}
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }}
|
||||
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
||||
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
|
||||
FAL_KEY: ${{ secrets.FAL_KEY }}
|
||||
RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }}
|
||||
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
|
||||
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
|
||||
VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }}
|
||||
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
|
||||
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
|
||||
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
|
||||
BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }}
|
||||
BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }}
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }}
|
||||
OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }}
|
||||
OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }}
|
||||
OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }}
|
||||
OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }}
|
||||
OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }}
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
|
||||
package_telegram:
|
||||
name: Telegram package acceptance
|
||||
needs: resolve_package
|
||||
if: needs.resolve_package.outputs.telegram_enabled == 'true'
|
||||
uses: ./.github/workflows/npm-telegram-beta-e2e.yml
|
||||
with:
|
||||
package_spec: ${{ inputs.package_spec }}
|
||||
package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}
|
||||
package_label: openclaw@${{ needs.resolve_package.outputs.package_version }}
|
||||
harness_ref: ${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }}
|
||||
provider_mode: ${{ needs.resolve_package.outputs.telegram_mode }}
|
||||
scenario: ${{ inputs.telegram_scenarios }}
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
|
||||
summary:
|
||||
name: Verify package acceptance
|
||||
needs: [resolve_package, docker_acceptance, package_telegram]
|
||||
if: always()
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify package acceptance results
|
||||
env:
|
||||
DOCKER_RESULT: ${{ needs.docker_acceptance.result }}
|
||||
PACKAGE_TELEGRAM_RESULT: ${{ needs.package_telegram.result }}
|
||||
RESOLVE_RESULT: ${{ needs.resolve_package.result }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
for item in \
|
||||
"resolve_package=${RESOLVE_RESULT}" \
|
||||
"docker_acceptance=${DOCKER_RESULT}" \
|
||||
"package_telegram=${PACKAGE_TELEGRAM_RESULT}"
|
||||
do
|
||||
name="${item%%=*}"
|
||||
result="${item#*=}"
|
||||
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
|
||||
echo "::error::${name} ended with ${result}"
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit "$failed"
|
||||
6
.github/workflows/parity-gate.yml
vendored
6
.github/workflows/parity-gate.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
# followthrough gate that expects a fast post-approval read within a 30s
|
||||
# agent.wait timeout.
|
||||
QA_PARITY_CONCURRENCY: "1"
|
||||
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL || 'openai/gpt-5.5' }}
|
||||
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL }}
|
||||
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
|
||||
OPENAI_API_KEY: ""
|
||||
ANTHROPIC_API_KEY: ""
|
||||
@@ -57,11 +57,9 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
|
||||
15
.github/workflows/plugin-clawhub-release.yml
vendored
15
.github/workflows/plugin-clawhub-release.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
ref_revision: ${{ steps.ref.outputs.sha }}
|
||||
ref_sha: ${{ steps.ref.outputs.sha }}
|
||||
has_candidates: ${{ steps.plan.outputs.has_candidates }}
|
||||
candidate_count: ${{ steps.plan.outputs.candidate_count }}
|
||||
skipped_published_count: ${{ steps.plan.outputs.skipped_published_count }}
|
||||
@@ -44,7 +44,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -151,8 +150,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
|
||||
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -166,7 +164,6 @@ jobs:
|
||||
- name: Checkout ClawHub CLI source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: ${{ env.CLAWHUB_REPOSITORY }}
|
||||
ref: ${{ env.CLAWHUB_REF }}
|
||||
path: clawhub-source
|
||||
@@ -190,7 +187,7 @@ jobs:
|
||||
env:
|
||||
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
|
||||
SOURCE_REPO: ${{ github.repository }}
|
||||
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
|
||||
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
|
||||
SOURCE_REF: ${{ github.ref }}
|
||||
PACKAGE_TAG: ${{ matrix.plugin.publishTag }}
|
||||
PACKAGE_DIR: ${{ matrix.plugin.packageDir }}
|
||||
@@ -212,8 +209,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
|
||||
ref: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -227,7 +223,6 @@ jobs:
|
||||
- name: Checkout ClawHub CLI source
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: ${{ env.CLAWHUB_REPOSITORY }}
|
||||
ref: ${{ env.CLAWHUB_REF }}
|
||||
path: clawhub-source
|
||||
@@ -271,7 +266,7 @@ jobs:
|
||||
env:
|
||||
CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }}
|
||||
SOURCE_REPO: ${{ github.repository }}
|
||||
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_revision }}
|
||||
SOURCE_COMMIT: ${{ needs.preview_plugins_clawhub.outputs.ref_sha }}
|
||||
SOURCE_REF: ${{ github.ref }}
|
||||
PACKAGE_TAG: ${{ matrix.plugin.publishTag }}
|
||||
PACKAGE_DIR: ${{ matrix.plugin.packageDir }}
|
||||
|
||||
9
.github/workflows/plugin-npm-release.yml
vendored
9
.github/workflows/plugin-npm-release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
ref_revision: ${{ steps.ref.outputs.sha }}
|
||||
ref_sha: ${{ steps.ref.outputs.sha }}
|
||||
has_candidates: ${{ steps.plan.outputs.has_candidates }}
|
||||
candidate_count: ${{ steps.plan.outputs.candidate_count }}
|
||||
matrix: ${{ steps.plan.outputs.matrix }}
|
||||
@@ -54,7 +54,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -152,8 +151,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_revision }}
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -187,8 +185,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_revision }}
|
||||
ref: ${{ needs.preview_plugins_npm.outputs.ref_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
|
||||
413
.github/workflows/plugin-prerelease.yml
vendored
413
.github/workflows/plugin-prerelease.yml
vendored
@@ -1,413 +0,0 @@
|
||||
name: Plugin Prerelease
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target_ref:
|
||||
description: Branch, tag, or full commit SHA to validate
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
expected_sha:
|
||||
description: Optional full commit SHA that target_ref must resolve to
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
full_release_validation:
|
||||
description: Enable release-only Docker prerelease lanes from Full Release Validation
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: plugin-prerelease-${{ inputs.target_ref }}
|
||||
cancel-in-progress: ${{ inputs.target_ref == 'main' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
name: Build plugin prerelease plan
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
checkout_revision: ${{ steps.manifest.outputs.checkout_revision }}
|
||||
run_plugin_prerelease_suite: ${{ steps.manifest.outputs.run_plugin_prerelease_suite }}
|
||||
run_plugin_prerelease_static: ${{ steps.manifest.outputs.run_plugin_prerelease_static }}
|
||||
plugin_prerelease_static_matrix: ${{ steps.manifest.outputs.plugin_prerelease_static_matrix }}
|
||||
run_plugin_prerelease_node: ${{ steps.manifest.outputs.run_plugin_prerelease_node }}
|
||||
plugin_prerelease_node_matrix: ${{ steps.manifest.outputs.plugin_prerelease_node_matrix }}
|
||||
run_plugin_prerelease_extensions: ${{ steps.manifest.outputs.run_plugin_prerelease_extensions }}
|
||||
plugin_prerelease_extension_matrix: ${{ steps.manifest.outputs.plugin_prerelease_extension_matrix }}
|
||||
run_plugin_prerelease_docker: ${{ steps.manifest.outputs.run_plugin_prerelease_docker }}
|
||||
plugin_prerelease_docker_lanes: ${{ steps.manifest.outputs.plugin_prerelease_docker_lanes }}
|
||||
steps:
|
||||
- name: Checkout target
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.target_ref }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Build plugin prerelease manifest
|
||||
id: manifest
|
||||
env:
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
FULL_RELEASE_VALIDATION: ${{ inputs.full_release_validation && 'true' || 'false' }}
|
||||
run: |
|
||||
node --input-type=module <<'EOF'
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const createMatrix = (include) => ({ include });
|
||||
const outputPath = process.env.GITHUB_OUTPUT;
|
||||
const checkoutRevision = execFileSync("git", ["rev-parse", "HEAD"], {
|
||||
encoding: "utf8",
|
||||
}).trim();
|
||||
const expectedSha = (process.env.EXPECTED_SHA ?? "").trim();
|
||||
const fullReleaseValidation = process.env.FULL_RELEASE_VALIDATION === "true";
|
||||
if (expectedSha && expectedSha !== checkoutRevision) {
|
||||
console.error(
|
||||
`target_ref resolved to ${checkoutRevision}, expected ${expectedSha}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let pluginPrereleasePlan = { staticChecks: [], dockerLanes: [] };
|
||||
let extensionShards = [];
|
||||
let nodeShards = [];
|
||||
|
||||
try {
|
||||
const { assertPluginPrereleaseTestPlanComplete } = await import(
|
||||
"./scripts/lib/plugin-prerelease-test-plan.mjs"
|
||||
);
|
||||
pluginPrereleasePlan = assertPluginPrereleaseTestPlanComplete();
|
||||
} catch (error) {
|
||||
const errorCode =
|
||||
error && typeof error === "object" && "code" in error ? error.code : "";
|
||||
const moduleUrl =
|
||||
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
||||
if (
|
||||
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
||||
moduleUrl.endsWith("/scripts/lib/plugin-prerelease-test-plan.mjs")
|
||||
) {
|
||||
console.warn(
|
||||
"Plugin prerelease plan unavailable in target ref; skipping static and Docker plugin prerelease lanes.",
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { createExtensionTestShards, DEFAULT_EXTENSION_TEST_SHARD_COUNT } = await import(
|
||||
"./scripts/lib/extension-test-plan.mjs"
|
||||
);
|
||||
extensionShards = createExtensionTestShards({
|
||||
shardCount: DEFAULT_EXTENSION_TEST_SHARD_COUNT,
|
||||
}).map((shard) => ({
|
||||
check_name: shard.checkName,
|
||||
extensions_csv: shard.extensionIds.join(","),
|
||||
runner: [0, 1, 2, 3].includes(shard.index)
|
||||
? "blacksmith-8vcpu-ubuntu-2404"
|
||||
: "blacksmith-4vcpu-ubuntu-2404",
|
||||
shard_index: shard.index + 1,
|
||||
task: "extensions-batch",
|
||||
}));
|
||||
} catch (error) {
|
||||
const errorCode =
|
||||
error && typeof error === "object" && "code" in error ? error.code : "";
|
||||
const moduleUrl =
|
||||
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
||||
if (
|
||||
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
||||
moduleUrl.endsWith("/scripts/lib/extension-test-plan.mjs")
|
||||
) {
|
||||
console.warn(
|
||||
"Extension test plan unavailable in target ref; skipping extension prerelease shards.",
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { createNodeTestShards } = await import("./scripts/lib/ci-node-test-plan.mjs");
|
||||
nodeShards = createNodeTestShards({
|
||||
includeReleaseOnlyPluginShards: true,
|
||||
})
|
||||
.filter((shard) => shard.shardName === "agentic-plugins")
|
||||
.map((shard) => ({
|
||||
check_name: shard.checkName,
|
||||
runtime: "node",
|
||||
task: "test-shard",
|
||||
shard_name: shard.shardName,
|
||||
configs: shard.configs,
|
||||
includePatterns: shard.includePatterns,
|
||||
runner: shard.runner,
|
||||
}));
|
||||
} catch (error) {
|
||||
const errorCode =
|
||||
error && typeof error === "object" && "code" in error ? error.code : "";
|
||||
const moduleUrl =
|
||||
error && typeof error === "object" && "url" in error ? String(error.url) : "";
|
||||
if (
|
||||
errorCode === "ERR_MODULE_NOT_FOUND" &&
|
||||
moduleUrl.endsWith("/scripts/lib/ci-node-test-plan.mjs")
|
||||
) {
|
||||
console.warn(
|
||||
"Node test plan unavailable in target ref; skipping release-only plugin Node shard.",
|
||||
);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const staticChecks = pluginPrereleasePlan.staticChecks.map((check) => ({
|
||||
check_name: check.checkName,
|
||||
command: check.command,
|
||||
task: check.check,
|
||||
}));
|
||||
const dockerLanes = pluginPrereleasePlan.dockerLanes;
|
||||
const runStatic = staticChecks.length > 0;
|
||||
const runNode = nodeShards.length > 0;
|
||||
const runExtensions = extensionShards.length > 0;
|
||||
const runDocker = fullReleaseValidation && dockerLanes.length > 0;
|
||||
const runSuite = runStatic || runNode || runExtensions || runDocker;
|
||||
|
||||
const manifest = {
|
||||
checkout_revision: checkoutRevision,
|
||||
run_plugin_prerelease_suite: runSuite,
|
||||
run_plugin_prerelease_static: runStatic,
|
||||
plugin_prerelease_static_matrix: createMatrix(staticChecks),
|
||||
run_plugin_prerelease_node: runNode,
|
||||
plugin_prerelease_node_matrix: createMatrix(nodeShards),
|
||||
run_plugin_prerelease_extensions: runExtensions,
|
||||
plugin_prerelease_extension_matrix: createMatrix(extensionShards),
|
||||
run_plugin_prerelease_docker: runDocker,
|
||||
plugin_prerelease_docker_lanes: dockerLanes.join(" "),
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(manifest)) {
|
||||
appendFileSync(
|
||||
outputPath,
|
||||
`${key}=${typeof value === "string" ? value : JSON.stringify(value)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
EOF
|
||||
|
||||
plugin-prerelease-static-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_plugin_prerelease_static == 'true'
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.plugin_prerelease_static_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Run plugin prerelease static shard
|
||||
env:
|
||||
PLUGIN_PRERELEASE_COMMAND: ${{ matrix.command }}
|
||||
PLUGIN_PRERELEASE_TASK: ${{ matrix.task }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "Running ${PLUGIN_PRERELEASE_TASK}: ${PLUGIN_PRERELEASE_COMMAND}"
|
||||
bash -c "$PLUGIN_PRERELEASE_COMMAND"
|
||||
|
||||
plugin-prerelease-node-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_plugin_prerelease_node == 'true'
|
||||
runs-on: ${{ matrix.runner || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.plugin_prerelease_node_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Configure Node test resources
|
||||
run: echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run release-only plugin Node shard
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }}
|
||||
OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
|
||||
OPENCLAW_VITEST_SHARD_NAME: ${{ matrix.shard_name }}
|
||||
OPENCLAW_TEST_PROJECTS_PARALLEL: "2"
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node --input-type=module <<'EOF'
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const configs = JSON.parse(process.env.OPENCLAW_NODE_TEST_CONFIGS_JSON ?? "[]");
|
||||
if (!Array.isArray(configs) || configs.length === 0) {
|
||||
console.error("Missing node test shard configs");
|
||||
process.exit(1);
|
||||
}
|
||||
const includePatterns = JSON.parse(
|
||||
process.env.OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON ?? "null",
|
||||
);
|
||||
const childEnv = { ...process.env };
|
||||
if (Array.isArray(includePatterns) && includePatterns.length > 0) {
|
||||
const includeFile = join(
|
||||
process.env.RUNNER_TEMP ?? ".",
|
||||
`node-test-include-${process.env.GITHUB_JOB ?? "local"}-${Date.now()}.json`,
|
||||
);
|
||||
writeFileSync(includeFile, JSON.stringify(includePatterns), "utf8");
|
||||
childEnv.OPENCLAW_VITEST_INCLUDE_FILE = includeFile;
|
||||
}
|
||||
|
||||
const result = spawnSync(
|
||||
"pnpm",
|
||||
["exec", "node", "scripts/test-projects.mjs", ...configs],
|
||||
{
|
||||
env: childEnv,
|
||||
stdio: "inherit",
|
||||
},
|
||||
);
|
||||
process.exit(result.status ?? 1);
|
||||
EOF
|
||||
|
||||
plugin-prerelease-extension-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_plugin_prerelease_extensions == 'true'
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.plugin_prerelease_extension_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Run extension shard
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
OPENCLAW_EXTENSION_BATCH_PARALLEL: 2
|
||||
OPENCLAW_VITEST_MAX_WORKERS: 1
|
||||
OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }}
|
||||
run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH"
|
||||
|
||||
plugin-prerelease-docker-suite:
|
||||
name: plugin-prerelease-docker-suite
|
||||
needs: [preflight]
|
||||
if: ${{ inputs.full_release_validation && needs.preflight.outputs.run_plugin_prerelease_docker == 'true' }}
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ needs.preflight.outputs.checkout_revision }}
|
||||
include_repo_e2e: false
|
||||
include_release_path_suites: false
|
||||
include_openwebui: false
|
||||
docker_lanes: ${{ needs.preflight.outputs.plugin_prerelease_docker_lanes }}
|
||||
include_live_suites: false
|
||||
live_models_only: false
|
||||
|
||||
plugin-prerelease-suite:
|
||||
permissions:
|
||||
contents: read
|
||||
name: plugin-prerelease-suite
|
||||
needs:
|
||||
- preflight
|
||||
- plugin-prerelease-static-shard
|
||||
- plugin-prerelease-node-shard
|
||||
- plugin-prerelease-extension-shard
|
||||
- plugin-prerelease-docker-suite
|
||||
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_plugin_prerelease_suite == 'true' }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify plugin prerelease suite
|
||||
env:
|
||||
RUN_STATIC: ${{ needs.preflight.outputs.run_plugin_prerelease_static }}
|
||||
RUN_NODE: ${{ needs.preflight.outputs.run_plugin_prerelease_node }}
|
||||
RUN_EXTENSIONS: ${{ needs.preflight.outputs.run_plugin_prerelease_extensions }}
|
||||
RUN_DOCKER: ${{ needs.preflight.outputs.run_plugin_prerelease_docker }}
|
||||
STATIC_RESULT: ${{ needs.plugin-prerelease-static-shard.result }}
|
||||
NODE_RESULT: ${{ needs.plugin-prerelease-node-shard.result }}
|
||||
EXTENSIONS_RESULT: ${{ needs.plugin-prerelease-extension-shard.result }}
|
||||
DOCKER_RESULT: ${{ needs.plugin-prerelease-docker-suite.result }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
check_required() {
|
||||
local name="$1"
|
||||
local required="$2"
|
||||
local status="$3"
|
||||
if [ "$required" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$status" != "success" ]; then
|
||||
echo "::error::${name} ended with ${status}"
|
||||
failed=1
|
||||
fi
|
||||
}
|
||||
|
||||
check_required "plugin-prerelease-static" "$RUN_STATIC" "$STATIC_RESULT"
|
||||
check_required "plugin-prerelease-node" "$RUN_NODE" "$NODE_RESULT"
|
||||
check_required "plugin-prerelease-extensions" "$RUN_EXTENSIONS" "$EXTENSIONS_RESULT"
|
||||
check_required "plugin-prerelease-docker" "$RUN_DOCKER" "$DOCKER_RESULT"
|
||||
exit "$failed"
|
||||
151
.github/workflows/qa-live-transports-convex.yml
vendored
151
.github/workflows/qa-live-transports-convex.yml
vendored
@@ -18,19 +18,6 @@ on:
|
||||
description: Optional comma-separated Discord scenario ids
|
||||
required: false
|
||||
type: string
|
||||
matrix_profile:
|
||||
description: Matrix QA profile for the live Matrix lane
|
||||
required: false
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- fast
|
||||
- all
|
||||
- transport
|
||||
- media
|
||||
- e2ee-smoke
|
||||
- e2ee-deep
|
||||
- e2ee-cli
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -44,7 +31,7 @@ env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.33.0"
|
||||
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL || 'openai/gpt-5.5' }}
|
||||
OPENCLAW_CI_OPENAI_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL }}
|
||||
OPENCLAW_BUILD_PRIVATE_QA: "1"
|
||||
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
|
||||
|
||||
@@ -81,13 +68,12 @@ jobs:
|
||||
needs: authorize_actor
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
outputs:
|
||||
selected_revision: ${{ steps.validate.outputs.selected_revision }}
|
||||
selected_sha: ${{ steps.validate.outputs.selected_sha }}
|
||||
trusted_reason: ${{ steps.validate.outputs.trusted_reason }}
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -99,27 +85,27 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
selected_revision="$(git rev-parse HEAD)"
|
||||
selected_sha="$(git rev-parse HEAD)"
|
||||
trusted_reason=""
|
||||
|
||||
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
|
||||
if git merge-base --is-ancestor "$selected_sha" refs/remotes/origin/main; then
|
||||
trusted_reason="main-ancestor"
|
||||
elif git tag --points-at "$selected_revision" | grep -Eq '^v'; then
|
||||
elif git tag --points-at "$selected_sha" | grep -Eq '^v'; then
|
||||
trusted_reason="release-tag"
|
||||
elif [[ "$INPUT_REF" =~ ^release/[0-9]{4}\.[0-9]+\.[0-9]+$ ]]; then
|
||||
git fetch --no-tags origin "+refs/heads/${INPUT_REF}:refs/remotes/origin/${INPUT_REF}"
|
||||
release_branch_sha="$(git rev-parse "refs/remotes/origin/${INPUT_REF}")"
|
||||
if [[ "$selected_revision" == "$release_branch_sha" ]]; then
|
||||
if [[ "$selected_sha" == "$release_branch_sha" ]]; then
|
||||
trusted_reason="release-branch-head"
|
||||
fi
|
||||
else
|
||||
pr_head_count="$(
|
||||
gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"repos/${GITHUB_REPOSITORY}/commits/${selected_revision}/pulls" \
|
||||
--jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${selected_revision}"'")] | length'
|
||||
"repos/${GITHUB_REPOSITORY}/commits/${selected_sha}/pulls" \
|
||||
--jq '[.[] | select(.state == "open" and .head.repo.full_name == "'"${GITHUB_REPOSITORY}"'" and .head.sha == "'"${selected_sha}"'")] | length'
|
||||
)"
|
||||
if [[ "$pr_head_count" != "0" ]]; then
|
||||
trusted_reason="open-pr-head"
|
||||
@@ -127,23 +113,23 @@ jobs:
|
||||
fi
|
||||
|
||||
if [[ -z "$trusted_reason" ]]; then
|
||||
echo "Ref '${INPUT_REF}' resolved to $selected_revision, which is not trusted for this secret-bearing QA run." >&2
|
||||
echo "Ref '${INPUT_REF}' resolved to $selected_sha, which is not trusted for this secret-bearing QA run." >&2
|
||||
echo "Allowed refs must be on main, point to a release tag, match a release branch head, or match an open PR head in ${GITHUB_REPOSITORY}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "selected_revision=$selected_revision" >> "$GITHUB_OUTPUT"
|
||||
echo "selected_sha=$selected_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "trusted_reason=$trusted_reason" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "Validated ref: \`${INPUT_REF}\`"
|
||||
echo "Resolved SHA: \`$selected_revision\`"
|
||||
echo "Resolved SHA: \`$selected_sha\`"
|
||||
echo "Trust reason: \`$trusted_reason\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
run_mock_parity:
|
||||
name: Run QA Lab parity gate
|
||||
needs: [validate_selected_ref]
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
QA_PARITY_CONCURRENCY: "1"
|
||||
@@ -158,8 +144,7 @@ jobs:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -214,16 +199,14 @@ jobs:
|
||||
run_live_matrix:
|
||||
name: Run Matrix live QA lane
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.matrix_profile == 'all') }}
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -253,29 +236,20 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
INPUT_MATRIX_PROFILE: ${{ github.event_name == 'workflow_dispatch' && inputs.matrix_profile || 'fast' }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
output_dir=".artifacts/qa-e2e/matrix-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
matrix_args=(
|
||||
pnpm openclaw qa matrix \
|
||||
--repo-root . \
|
||||
--output-dir "${output_dir}" \
|
||||
--provider-mode live-frontier \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--profile "${INPUT_MATRIX_PROFILE}" \
|
||||
--fast
|
||||
)
|
||||
if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then
|
||||
matrix_args+=(--fail-fast)
|
||||
fi
|
||||
|
||||
pnpm openclaw qa matrix "${matrix_args[@]}"
|
||||
|
||||
- name: Upload Matrix QA artifacts
|
||||
if: always()
|
||||
@@ -286,101 +260,17 @@ jobs:
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
run_live_matrix_sharded:
|
||||
name: Run Matrix live QA lane (${{ matrix.profile }})
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.matrix_profile == 'all' }}
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
profile:
|
||||
- transport
|
||||
- media
|
||||
- e2ee-smoke
|
||||
- e2ee-deep
|
||||
- e2ee-cli
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "true"
|
||||
|
||||
- name: Validate required QA credential env
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
||||
echo "Missing required OPENAI_API_KEY." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Matrix live lane shard
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
|
||||
OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS: "3000"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
output_dir=".artifacts/qa-e2e/matrix-live-${{ matrix.profile }}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
matrix_args=(
|
||||
--repo-root . \
|
||||
--output-dir "${output_dir}" \
|
||||
--provider-mode live-frontier \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--profile "${{ matrix.profile }}" \
|
||||
--fast
|
||||
)
|
||||
if pnpm openclaw qa matrix --help 2>/dev/null | grep -F -q -- "--fail-fast"; then
|
||||
matrix_args+=(--fail-fast)
|
||||
fi
|
||||
|
||||
pnpm openclaw qa matrix "${matrix_args[@]}"
|
||||
|
||||
- name: Upload Matrix QA shard artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qa-live-matrix-${{ matrix.profile }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
run_live_telegram:
|
||||
name: Run Telegram live QA lane with Convex leases
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -465,15 +355,14 @@ jobs:
|
||||
run_live_discord:
|
||||
name: Run Discord live QA lane with Convex leases
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
steps:
|
||||
- name: Checkout selected ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node environment
|
||||
|
||||
110
.github/workflows/stale.yml
vendored
110
.github/workflows/stale.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
with:
|
||||
app-id: "2971289"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
|
||||
- name: Mark stale unassigned issues and pull requests (primary)
|
||||
- name: Mark stale issues and pull requests (primary)
|
||||
id: stale-primary
|
||||
continue-on-error: true
|
||||
uses: actions/stale@v10
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
days-before-pr-close: 3
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
@@ -56,59 +56,11 @@ jobs:
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh repro steps.
|
||||
close-issue-reason: not_planned
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
- name: Mark stale assigned issues (primary)
|
||||
id: assigned-issue-stale-primary
|
||||
continue-on-error: true
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 10
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
stale-issue-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
remove-stale-when-updated: true
|
||||
stale-issue-message: |
|
||||
This assigned issue has been automatically marked as stale after 30 days of inactivity.
|
||||
Please add updates or it will be closed.
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
close-issue-reason: not_planned
|
||||
- name: Mark stale assigned pull requests (primary)
|
||||
id: assigned-stale-primary
|
||||
continue-on-error: true
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 27
|
||||
days-before-pr-close: 3
|
||||
stale-pr-label: stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
ignore-pr-updates: true
|
||||
remove-stale-when-updated: true
|
||||
stale-pr-message: |
|
||||
This assigned pull request has been automatically marked as stale after being open for 27 days.
|
||||
Please add updates or it will be closed.
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
- name: Check stale state cache
|
||||
id: stale-state
|
||||
@@ -134,7 +86,7 @@ jobs:
|
||||
core.warning(`Failed to check stale state cache: ${message}`);
|
||||
core.setOutput("has_state", "false");
|
||||
}
|
||||
- name: Mark stale unassigned issues and pull requests (fallback)
|
||||
- name: Mark stale issues and pull requests (fallback)
|
||||
if: (steps.stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != ''
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
@@ -145,7 +97,7 @@ jobs:
|
||||
days-before-pr-close: 3
|
||||
stale-issue-label: stale
|
||||
stale-pr-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
@@ -160,57 +112,11 @@ jobs:
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh repro steps.
|
||||
close-issue-reason: not_planned
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
- name: Mark stale assigned issues (fallback)
|
||||
if: (steps.assigned-issue-stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != ''
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 10
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
stale-issue-label: stale
|
||||
exempt-issue-labels: enhancement,maintainer,pinned,security,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
remove-stale-when-updated: true
|
||||
stale-issue-message: |
|
||||
This assigned issue has been automatically marked as stale after 30 days of inactivity.
|
||||
Please add updates or it will be closed.
|
||||
close-issue-message: |
|
||||
Closing due to inactivity.
|
||||
If this is still an issue, please retry on the latest OpenClaw release and share updated details.
|
||||
If you are absolutely sure it still happens on the latest release, open a new issue with fresh steps to reproduce.
|
||||
close-issue-reason: not_planned
|
||||
- name: Mark stale assigned pull requests (fallback)
|
||||
if: (steps.assigned-stale-primary.outcome == 'failure' || steps.stale-state.outputs.has_state == 'true') && steps.app-token-fallback.outputs.token != ''
|
||||
uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: -1
|
||||
days-before-issue-close: -1
|
||||
days-before-pr-stale: 27
|
||||
days-before-pr-close: 3
|
||||
stale-pr-label: stale
|
||||
exempt-pr-labels: maintainer,no-stale,bad-barnacle
|
||||
operations-per-run: 2000
|
||||
ascending: true
|
||||
include-only-assigned: true
|
||||
ignore-pr-updates: true
|
||||
remove-stale-when-updated: true
|
||||
stale-pr-message: |
|
||||
This assigned pull request has been automatically marked as stale after being open for 27 days.
|
||||
Please add updates or it will be closed.
|
||||
close-pr-message: |
|
||||
Closing due to inactivity.
|
||||
If you believe this PR should be revived, post in #clawtributors on Discord to talk to a maintainer.
|
||||
If you believe this PR should be revived, post in #pr-thunderdome-dangerzone on Discord to talk to a maintainer.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
|
||||
lock-closed-issues:
|
||||
|
||||
5
.github/workflows/test-performance-agent.yml
vendored
5
.github/workflows/test-performance-agent.yml
vendored
@@ -129,7 +129,7 @@ jobs:
|
||||
|
||||
- name: Run Codex test performance agent
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
|
||||
uses: openai/codex-action@v1
|
||||
with:
|
||||
openai-api-key: ${{ secrets.OPENCLAW_TEST_PERF_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
||||
prompt-file: .github/codex/prompts/test-performance-agent.md
|
||||
@@ -181,8 +181,7 @@ jobs:
|
||||
|
||||
- name: Restore Node 24 path
|
||||
if: steps.gate.outputs.run_agent == 'true' && steps.patch.outputs.has_changes == 'true'
|
||||
run:
|
||||
| # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job
|
||||
run: | # zizmor: ignore[github-env] NODE_BIN is set by the trusted local setup-node-env action in this same job
|
||||
set -euo pipefail
|
||||
export PATH="${NODE_BIN}:${PATH}"
|
||||
echo "${NODE_BIN}" >> "$GITHUB_PATH"
|
||||
|
||||
38
.gitignore
vendored
38
.gitignore
vendored
@@ -3,7 +3,6 @@ node_modules
|
||||
.env
|
||||
docker-compose.override.yml
|
||||
docker-compose.extra.yml
|
||||
docker-compose.sandbox.yml
|
||||
dist
|
||||
dist-runtime/
|
||||
pnpm-lock.yaml
|
||||
@@ -98,40 +97,6 @@ USER.md
|
||||
# local tooling
|
||||
.serena/
|
||||
|
||||
# Local project-agent skill installs. Only repo-owned skills are visible by
|
||||
# default; promoting a new repo skill should require an intentional `git add -f`.
|
||||
.agents/skills/*
|
||||
!.agents/skills/blacksmith-testbox/
|
||||
!.agents/skills/blacksmith-testbox/**
|
||||
!.agents/skills/gitcrawl/
|
||||
!.agents/skills/gitcrawl/**
|
||||
!.agents/skills/openclaw-ghsa-maintainer/
|
||||
!.agents/skills/openclaw-ghsa-maintainer/**
|
||||
!.agents/skills/openclaw-parallels-smoke/
|
||||
!.agents/skills/openclaw-parallels-smoke/**
|
||||
!.agents/skills/openclaw-pr-maintainer/
|
||||
!.agents/skills/openclaw-pr-maintainer/**
|
||||
!.agents/skills/openclaw-qa-testing/
|
||||
!.agents/skills/openclaw-qa-testing/**
|
||||
!.agents/skills/openclaw-release-maintainer/
|
||||
!.agents/skills/openclaw-release-maintainer/**
|
||||
!.agents/skills/openclaw-secret-scanning-maintainer/
|
||||
!.agents/skills/openclaw-secret-scanning-maintainer/**
|
||||
!.agents/skills/openclaw-test-heap-leaks/
|
||||
!.agents/skills/openclaw-test-heap-leaks/**
|
||||
!.agents/skills/openclaw-test-performance/
|
||||
!.agents/skills/openclaw-test-performance/**
|
||||
!.agents/skills/openclaw-testing/
|
||||
!.agents/skills/openclaw-testing/**
|
||||
!.agents/skills/optimizetests/
|
||||
!.agents/skills/optimizetests/**
|
||||
!.agents/skills/parallels-discord-roundtrip/
|
||||
!.agents/skills/parallels-discord-roundtrip/**
|
||||
!.agents/skills/security-triage/
|
||||
!.agents/skills/security-triage/**
|
||||
!.agents/skills/tag-duplicate-prs-issues/
|
||||
!.agents/skills/tag-duplicate-prs-issues/**
|
||||
|
||||
# Agent credentials and memory (NEVER COMMIT)
|
||||
/memory/
|
||||
.agent/*.json
|
||||
@@ -191,6 +156,3 @@ extensions/qa-lab/web/dist/
|
||||
# Generated bundled plugin runtime dependency manifests
|
||||
extensions/**/.openclaw-runtime-deps.json
|
||||
extensions/**/.openclaw-runtime-deps-stamp.json
|
||||
|
||||
# Output dir for scripts/run-opengrep.sh (local opengrep scans)
|
||||
/.opengrep-out/
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"eslint/no-sequences": "error",
|
||||
"eslint/no-self-compare": "error",
|
||||
"eslint/no-shadow": "off",
|
||||
"eslint/no-underscore-dangle": "off",
|
||||
"eslint/no-var": "error",
|
||||
"eslint/no-useless-call": "error",
|
||||
"eslint/no-useless-computed-key": "error",
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
# .semgrepignore — single source of truth for paths excluded from
|
||||
# opengrep / semgrep scans run against this repo.
|
||||
#
|
||||
# Syntax: gitignore-style globs (https://git-scm.com/docs/gitignore).
|
||||
# Consumed automatically by `opengrep scan` and `semgrep scan`. The compiled
|
||||
# detector rulepacks under security/opengrep/ and the GitHub Actions workflows
|
||||
# under .github/workflows/opengrep-*.yml all rely on this file rather than
|
||||
# duplicating exclude lists in 50+ places.
|
||||
#
|
||||
# When adding a new test naming convention, fixture directory, or QA-tooling
|
||||
# extension to the codebase, add its glob here so the security rulepacks
|
||||
# stop firing on it. Real product code should never match anything in this
|
||||
# file.
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Standard test file suffixes
|
||||
# ----------------------------------------------------------------------------
|
||||
*.test.*
|
||||
*.spec.*
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Fixture & mock file suffixes (cover both .foo and -foo styles used in repo)
|
||||
# ----------------------------------------------------------------------------
|
||||
*.fixture.*
|
||||
*-fixture.*
|
||||
*-fixtures.*
|
||||
*.mock.*
|
||||
*-mock.*
|
||||
*-mocks.*
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Test helper / harness / support / shared / utils naming conventions
|
||||
# ----------------------------------------------------------------------------
|
||||
*.test-helper.*
|
||||
*.test-helpers.*
|
||||
*-test-helpers.*
|
||||
*.test-harness.*
|
||||
*-test-harness.*
|
||||
*.test-support.*
|
||||
*-test-support.*
|
||||
*.test-shared.*
|
||||
*-test-shared.*
|
||||
*.test-mocks.*
|
||||
*-test-mocks.*
|
||||
*.test-utils.*
|
||||
*-test-utils.*
|
||||
*.test-fixtures.*
|
||||
*-test-fixtures.*
|
||||
*.e2e-test-helpers.*
|
||||
|
||||
# Bare top-of-dir test helper files (e.g. extensions/foo/src/test-helpers.ts)
|
||||
test-helper.*
|
||||
test-helpers.*
|
||||
test-harness.*
|
||||
test-support.*
|
||||
test-shared.*
|
||||
test-utils.*
|
||||
test-mocks.*
|
||||
test-fixtures.*
|
||||
test-fetch.*
|
||||
test-manager-helpers.*
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Test / mock / fixture directories anywhere in the tree
|
||||
# ----------------------------------------------------------------------------
|
||||
__tests__/
|
||||
__mocks__/
|
||||
test/
|
||||
tests/
|
||||
test-fixtures/
|
||||
test-fixture/
|
||||
test-helpers/
|
||||
test-utils/
|
||||
test-support/
|
||||
test-mocks/
|
||||
test-harness/
|
||||
fixtures/
|
||||
mocks/
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# QA tooling — entire QA-only directories and extensions, not product code
|
||||
# ----------------------------------------------------------------------------
|
||||
qa/
|
||||
qa-lab/
|
||||
extensions/qa-*/
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Top-level scripts that drive tests rather than ship product behavior
|
||||
# ----------------------------------------------------------------------------
|
||||
scripts/test-*
|
||||
scripts/run-vitest*
|
||||
scripts/run-tests*
|
||||
scripts/lib/test-*
|
||||
scripts/lib/extension-test-*
|
||||
scripts/lib/vitest-*
|
||||
35
AGENTS.md
35
AGENTS.md
@@ -29,7 +29,6 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Extension prod code: no core `src/**`, `src/plugin-sdk-internal/**`, other extension `src/**`, or relative outside package.
|
||||
- Core/tests: no deep plugin internals (`extensions/*/src/**`, `onboard.js`). Use `api.ts`, SDK facade, generic contracts.
|
||||
- Extension-owned behavior stays extension-owned: repair, detection, onboarding, auth/provider defaults, provider tools/settings.
|
||||
- Owner boundary: fix owner-specific behavior in the owner module. Shared/core gets generic seams only; no owner ids, dependency strings, defaults, migrations, or recovery policy. If a bug names an extension or its dependency, start in that extension and add a generic core seam only when multiple owners need it.
|
||||
- Legacy config repair: doctor/fix paths, not startup/load-time core migrations.
|
||||
- Core test asserting extension-specific behavior: move to owner extension or generic contract test.
|
||||
- New seams: backwards-compatible, documented, versioned. Third-party plugins exist.
|
||||
@@ -50,21 +49,15 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Prod sweep: `pnpm check`; tests: `pnpm test`, `pnpm test:changed`, `pnpm test:serial`, `pnpm test:coverage`.
|
||||
- Extension tests: `pnpm test:extensions`, `pnpm test extensions`, `pnpm test extensions/<id>`.
|
||||
- Targeted tests: `pnpm test <path-or-filter> [vitest args...]`; never raw `vitest`.
|
||||
- Vitest flags only; no Jest flags like `--runInBand`. For serial runs use `pnpm test:serial` or `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test ...`.
|
||||
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); do not add `tsc --noEmit`, `typecheck`, `check:types`.
|
||||
- Formatting: use `oxfmt`, not Prettier. Prefer `pnpm format:check` / `pnpm format`; for targeted files use `pnpm exec oxfmt --check --threads=1 <files...>` or `pnpm exec oxfmt --write --threads=1 <files...>`.
|
||||
- Linting: use repo wrappers (`pnpm lint:*`, `scripts/run-oxlint.mjs`); do not invoke generic JS formatters/lints unless a repo script uses them.
|
||||
- Format/lint: `pnpm format:check`/`pnpm format`; `pnpm lint*` lanes.
|
||||
- Heavy checks: `OPENCLAW_LOCAL_CHECK=1`, mode `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`; CI/shared use `OPENCLAW_LOCAL_CHECK=0`.
|
||||
- Blacksmith/Testbox: on maintainer machines with Blacksmith access, broad/shared validation defaults to Testbox. This includes `pnpm check`, `pnpm check:changed`, `pnpm test`, `pnpm test:changed`, Docker/E2E/live/package/build gates, and any command likely to fan out across many Vitest projects. Do not start those broad gates locally unless the user explicitly asks for local proof or sets `OPENCLAW_LOCAL_CHECK_MODE=throttled|full`.
|
||||
- Local validation: targeted edit loops only, such as `pnpm test <specific-file>`, targeted formatter checks, and small lint/type probes. If a local command expands beyond targeted proof, stop it and move the broad gate to Testbox.
|
||||
- Testbox use: run from repo root, pre-warm early with `blacksmith testbox warmup ci-check-testbox.yml --ref main --idle-timeout 90`, reuse the returned `tbx_...` id for all `run`/`download` commands, and stop boxes you created before handoff. Timeout bins: `90` minutes default, `240` multi-hour, `720` all-day, `1440` overnight; anything above `1440` needs explicit approval and cleanup.
|
||||
- Testbox full-suite profile: `blacksmith testbox run --id <ID> "env NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_TEST_PROJECTS_PARALLEL=6 OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test"`. For installable package proof, prefer the GitHub `Package Acceptance` workflow over ad hoc Testbox commands.
|
||||
- Local first. Use repo `pnpm` lanes before Blacksmith/Testbox. Remote only for parity-only failures, secrets/services, or explicit ask.
|
||||
|
||||
## GitHub / CI
|
||||
|
||||
- Triage: list first, hydrate few. Use bounded `gh --json --jq`; avoid repeated full comment scans.
|
||||
- Automatic PR/issue discovery: skip maintainer-owned items unless directly relevant. Do not comment, close, label, retitle, rebase, fix up, or land them without Peter asking.
|
||||
- PR scan/triage: no unsolicited PR comments/reviews. Report in chat only unless explicitly asked, or a close/duplicate action needs a reason comment.
|
||||
- Search/dedupe: prefer `gh search issues 'repo:openclaw/openclaw is:open <terms>' --json number,title,state,updatedAt --limit 20`.
|
||||
- GitHub search boolean text is fussy. If `OR` queries return empty, split exact terms and search title/body/comments separately before concluding no hits.
|
||||
- PR shortlist: `gh pr list ...`; then `gh pr view <n> --json number,title,body,closingIssuesReferences,files,statusCheckRollup,reviewDecision`.
|
||||
@@ -72,7 +65,6 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- GH comments with markdown backticks, `$`, or shell snippets: avoid inline double-quoted `--body`; use single quotes or `--body-file`.
|
||||
- PR execution artifacts/screenshots: attach them to the PR, comment, or an external artifact store. Do not add `.github/pr-assets` or other PR-only assets to the repo.
|
||||
- PR review answer must explicitly cover: what bug/behavior we are trying to fix; PR/issue URL(s) and affected endpoint/surface; whether this is the best possible fix, with high-certainty evidence from code, tests, CI, and shipped/current behavior.
|
||||
- When working on an issue or PR, always end the user-facing final answer with the full GitHub URL.
|
||||
- CI polling: exact SHA, needed fields only. Example: `gh api repos/<owner>/<repo>/actions/runs/<id> --jq '{status,conclusion,head_sha,updated_at,name,path}'`.
|
||||
- Post-land wait: minimal. Exact landed SHA only. If superseded on `main`, same-branch `cancel-in-progress` cancellations are expected; stop once local touched-surface proof exists. Never wait for newer unrelated `main` unless asked.
|
||||
- Wait matrix:
|
||||
@@ -93,16 +85,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- extension tests: extension test typecheck/tests
|
||||
- public SDK/plugin contract: extension prod/test too
|
||||
- unknown root/config: all lanes
|
||||
- Before handoff/push for code/test/runtime/config changes: run `pnpm check:changed` in Testbox by default on maintainer machines. Tests-only: run `pnpm test:changed` in Testbox by default. Full prod sweep: run `pnpm check` in Testbox. Use local only for narrow targeted proof or when explicitly requested.
|
||||
- If `pnpm test:changed` or `pnpm check:changed` selects broad/shared lanes, it belongs in Testbox; do not let it continue locally after it fans out.
|
||||
- Docs/changelog-only and CI/workflow metadata-only changes are not changed-gate work by default. Use `git diff --check` plus the relevant formatter/docs/workflow sanity check; escalate to `pnpm check:changed` only when scripts, test config, generated docs/API, package metadata, or runtime/build behavior changed.
|
||||
- Rebase sanity: after a green `pnpm check:changed`, a clean rebase onto current
|
||||
`origin/main` does not require rerunning the full changed gate when the rebase
|
||||
has no conflicts and the branch diff is materially unchanged. Do a quick
|
||||
`git status`, `git diff --check`, and diff/stat sanity check; rerun targeted or
|
||||
full checks only if conflict resolution, upstream overlap, generated drift,
|
||||
dependency/config changes, or touched-file content changes make the prior
|
||||
result stale.
|
||||
- Before handoff/push: `pnpm check:changed`. Tests-only: `pnpm test:changed`. Full prod sweep: `pnpm check`.
|
||||
- Landing on `main`: verify touched surface near landing. Default feasible bar: `pnpm check` + `pnpm test`.
|
||||
- Hard build gate: `pnpm build` before push if build output, packaging, lazy/module boundaries, or published surfaces can change.
|
||||
- Do not land related failing format/lint/type/build/tests. If unrelated on latest `origin/main`, say so with scoped proof.
|
||||
@@ -126,7 +109,6 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
## Tests
|
||||
|
||||
- Vitest. Colocated `*.test.ts`; e2e `*.e2e.test.ts`; example models `sonnet-4.6`, `gpt-5.4`.
|
||||
- Avoid brittle tests that grep workflow/docs strings for operator policy. Prefer executable behavior, parsed config/schema checks, or live run proof; put release/CI policy reminders in AGENTS/docs instead.
|
||||
- Clean timers/env/globals/mocks/sockets/temp dirs/module state; `--isolate=false` safe.
|
||||
- Hot tests: avoid per-test `vi.resetModules()` + heavy imports. Measure with `pnpm test:perf:imports <file>` / `pnpm test:perf:hotspots --limit N`.
|
||||
- Seam depth: pure helper/contract unit tests; one integration smoke per boundary.
|
||||
@@ -134,7 +116,6 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Prefer injection; if module mocking, mock narrow local `*.runtime.ts`, not broad barrels or `openclaw/plugin-sdk/*`.
|
||||
- Share fixtures/builders; delete duplicate assertions; assert behavior that can regress here.
|
||||
- Do not edit baseline/inventory/ignore/snapshot/expected-failure files to silence checks without explicit approval.
|
||||
- Do not run multiple independent `pnpm test`/Vitest commands concurrently in the same worktree. They can race on `node_modules/.experimental-vitest-cache` and fail with `ENOTEMPTY`. Use one grouped `pnpm test ...` invocation, run targeted lanes sequentially, or set distinct `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH` values when true parallel Vitest processes are needed.
|
||||
- Test workers max 16. Memory pressure: `OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test`.
|
||||
- Live: `OPENCLAW_LIVE_TEST=1 pnpm test:live`; verbose `OPENCLAW_LIVE_TEST_QUIET=0`.
|
||||
- Guide: `docs/help/testing.md`.
|
||||
@@ -143,7 +124,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
|
||||
- Docs change with behavior/API. Use docs list/read_when hints; docs links per `docs/AGENTS.md`.
|
||||
- Changelog user-facing only; pure test/internal usually no entry.
|
||||
- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @codex`, `Thanks @openclaw`, or `Thanks @steipete`.
|
||||
- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @steipete`.
|
||||
- Changelog bullets are always single-line. No wrapping/continuation across multiple lines. Long entries stay on one long line so dedupe, PR-ref, and credit-audit tooling work and so the visual style stays uniform.
|
||||
|
||||
## Git
|
||||
@@ -151,9 +132,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Commit via `scripts/committer "<msg>" <file...>`; stage intended files only. It formats staged files; still run gates.
|
||||
- Commits: conventional-ish, concise, grouped.
|
||||
- No manual stash/autostash unless explicit. No branch/worktree changes unless requested.
|
||||
- `main`: no merge commits; rebase on latest `origin/main` before push. Do not
|
||||
keep chasing `main` with repeated full gates after one green run plus a clean
|
||||
rebase sanity pass.
|
||||
- `main`: no merge commits; rebase on latest `origin/main` before push.
|
||||
- User says `commit`: your changes only. `commit all`: all changes in grouped chunks. `push`: may `git pull --rebase` first.
|
||||
- Do not delete/rename unexpected files; ask if blocking, else ignore.
|
||||
- Bulk PR close/reopen >5: ask with count/scope.
|
||||
@@ -175,7 +154,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
- Before simulator/emulator testing, check real iOS/Android devices.
|
||||
- "restart iOS/Android apps" = rebuild/reinstall/relaunch, not kill/launch.
|
||||
- SwiftUI: Observation (`@Observable`, `@Bindable`) over new `ObservableObject`.
|
||||
- Mac gateway: dev watch = `pnpm gateway:watch` (tmux `openclaw-gateway-watch-main`, auto-attach). Noninteractive: `OPENCLAW_GATEWAY_WATCH_ATTACH=0 pnpm gateway:watch`; attach/stop: `tmux attach -t openclaw-gateway-watch-main` / `tmux kill-session -t openclaw-gateway-watch-main`. Managed installs: `openclaw gateway restart/status --deep`. No launchd/ad-hoc tmux. Logs: `./scripts/clawlog.sh`.
|
||||
- Mac gateway: use app or `openclaw gateway restart/status --deep`; no ad-hoc tmux gateway. Logs: `./scripts/clawlog.sh`.
|
||||
- Version bump touches: `package.json`, `apps/android/app/build.gradle.kts`, `apps/ios/version.json` + `pnpm ios:version:sync`, macOS `Info.plist`, `docs/install/updating.md`. Appcast only for Sparkle release.
|
||||
- Mobile LAN pairing: plaintext `ws://` loopback-only. Private-network `ws://` needs `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1`; Tailscale/public use `wss://` or tunnel.
|
||||
- A2UI hash `src/canvas-host/a2ui/.bundle.hash`: generated; ignore unless running `pnpm canvas:a2ui:bundle`; commit separately.
|
||||
@@ -183,8 +162,6 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work.
|
||||
## Ops / Footguns
|
||||
|
||||
- Remote install docs: `docs/install/{exe-dev,fly,hetzner}.md`. Parallels smoke: `$openclaw-parallels-smoke`; Discord roundtrip: `parallels-discord-roundtrip`.
|
||||
- Memory wiki: keep prompt digest tiny. The prompt should only say the wiki exists, prefer `wiki_search` / `wiki_get`, start from `reports/person-agent-directory.md` for people routing, use search modes (`find-person`, `route-question`, `source-evidence`, `raw-claim`) when useful, and verify contact data before use.
|
||||
- People wiki provenance: generated identity, social, contact, and "fun detail" notes need explicit source class/confidence (`maintainer-whois`, Discrawl sample/stat, GitHub profile, maintainer repo file). Do not promote inferred details to facts.
|
||||
- Rebrand/migration/config warnings: run `openclaw doctor`.
|
||||
- Never edit `node_modules`.
|
||||
- Local-only `.agents` ignores: `.git/info/exclude`, not repo `.gitignore`.
|
||||
|
||||
1047
CHANGELOG.md
1047
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,7 @@ Welcome to the lobster tank! 🦞
|
||||
- **Tengji (George) Zhang** - Chinese model APIs, cloud, pi
|
||||
- GitHub: [@odysseus0](https://github.com/odysseus0) · X: [@odysseus0z](https://x.com/odysseus0z)
|
||||
|
||||
- **Sliverp** - Chinese Channel: QQ, WeChat, Wecom, Yuanbao, Dingtalk, Feishu
|
||||
- **Sliverp** - Chinese Channel: QQ, WeChat, Wecom, Dingtalk, Feishu
|
||||
- GitHub: [@sliverp](https://github.com/sliverp) · X: [@sliver01234](https://x.com/sliver01234)
|
||||
|
||||
- **Mason Huang** - Stability, Security, Speed
|
||||
|
||||
72
Dockerfile
72
Dockerfile
@@ -9,19 +9,22 @@
|
||||
# bundled plugin workspace tree, so the main build layer is not invalidated by
|
||||
# unrelated plugin source changes.
|
||||
#
|
||||
# Build stages use full bookworm; the runtime image is always bookworm-slim.
|
||||
# Two runtime variants:
|
||||
# Default (bookworm): docker build .
|
||||
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
|
||||
ARG OPENCLAW_EXTENSIONS=""
|
||||
ARG OPENCLAW_VARIANT=default
|
||||
ARG OPENCLAW_BUNDLED_PLUGIN_DIR=extensions
|
||||
ARG OPENCLAW_DOCKER_APT_UPGRADE=1
|
||||
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
|
||||
|
||||
# Base images are pinned to SHA256 digests for reproducible builds.
|
||||
# Dependabot refreshes these blessed digests; release builds consume the
|
||||
# reviewed base snapshot instead of mutating distro state on every build.
|
||||
# To update, run: docker buildx imagetools inspect node:24-bookworm and
|
||||
# node:24-bookworm-slim (or podman) and replace the digests below with the
|
||||
# current multi-arch manifest list entries.
|
||||
# Trade-off: digests must be updated manually when upstream tags move.
|
||||
# To update, run: docker buildx imagetools inspect node:24-bookworm (or podman)
|
||||
# and replace the digest below with the current multi-arch manifest list entry.
|
||||
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps
|
||||
ARG OPENCLAW_EXTENSIONS
|
||||
@@ -63,8 +66,6 @@ COPY openclaw.mjs ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY patches ./patches
|
||||
COPY scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/npm-runner.mjs scripts/windows-cmd-helpers.mjs ./scripts/
|
||||
COPY scripts/lib/bundled-runtime-deps-install.mjs ./scripts/lib/bundled-runtime-deps-install.mjs
|
||||
COPY scripts/lib/package-dist-imports.mjs ./scripts/lib/package-dist-imports.mjs
|
||||
|
||||
COPY --from=ext-deps /out/ ./${OPENCLAW_BUNDLED_PLUGIN_DIR}/
|
||||
|
||||
@@ -74,20 +75,10 @@ RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/sto
|
||||
NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
||||
|
||||
# pnpm v10+ may append peer-resolution hashes to virtual-store folder names; do not hardcode `.pnpm/...`
|
||||
# paths. Matrix's native downloader can hit transient release CDN errors while
|
||||
# still exiting successfully, so retry the package downloader before failing.
|
||||
RUN set -eux; \
|
||||
echo "==> Verifying critical native addons..."; \
|
||||
for attempt in 1 2 3 4 5; do \
|
||||
if find /app/node_modules -name "matrix-sdk-crypto*.node" 2>/dev/null | grep -q .; then \
|
||||
exit 0; \
|
||||
fi; \
|
||||
echo "matrix-sdk-crypto native addon missing; retrying download (${attempt}/5)"; \
|
||||
node /app/node_modules/@matrix-org/matrix-sdk-crypto-nodejs/download-lib.js || true; \
|
||||
sleep $((attempt * 2)); \
|
||||
done; \
|
||||
# paths. Fail fast here if the Matrix native binding did not materialize after install.
|
||||
RUN echo "==> Verifying critical native addons..." && \
|
||||
find /app/node_modules -name "matrix-sdk-crypto*.node" 2>/dev/null | grep -q . || \
|
||||
(echo "ERROR: matrix-sdk-crypto native addon missing after retries" >&2 && exit 1)
|
||||
(echo "ERROR: matrix-sdk-crypto native addon missing (pnpm install may have silently failed on this arch)" >&2 && exit 1)
|
||||
|
||||
COPY . .
|
||||
|
||||
@@ -132,18 +123,24 @@ RUN printf 'packages:\n - .\n - ui\n' > /tmp/pnpm-workspace.runtime.yaml && \
|
||||
cp /tmp/pnpm-workspace.runtime.yaml pnpm-workspace.yaml && \
|
||||
CI=true NPM_CONFIG_FROZEN_LOCKFILE=false pnpm prune --prod && \
|
||||
node scripts/postinstall-bundled-plugins.mjs && \
|
||||
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete && \
|
||||
node scripts/check-package-dist-imports.mjs /app
|
||||
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete
|
||||
|
||||
# ── Runtime base image ──────────────────────────────────────────
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-runtime
|
||||
# ── Runtime base images ─────────────────────────────────────────
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default
|
||||
ARG OPENCLAW_NODE_BOOKWORM_DIGEST
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm" \
|
||||
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_DIGEST}"
|
||||
|
||||
FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim
|
||||
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm-slim" \
|
||||
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST}"
|
||||
|
||||
# ── Stage 3: Runtime ────────────────────────────────────────────
|
||||
FROM base-runtime
|
||||
FROM base-${OPENCLAW_VARIANT}
|
||||
ARG OPENCLAW_VARIANT
|
||||
ARG OPENCLAW_BUNDLED_PLUGIN_DIR
|
||||
ARG OPENCLAW_DOCKER_APT_UPGRADE
|
||||
|
||||
# OCI base-image metadata for downstream image consumers.
|
||||
# If you change these annotations, also update:
|
||||
@@ -158,24 +155,24 @@ LABEL org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install runtime system utilities missing from bookworm-slim.
|
||||
# `ca-certificates` ships in `bookworm` (full) but not in `bookworm-slim`,
|
||||
# so it must be installed explicitly here. Without it `/etc/ssl/certs/`
|
||||
# stays empty and every HTTPS outbound dies at TLS handshake with
|
||||
# `error setting certificate file`.
|
||||
# Install system utilities present in bookworm but missing in bookworm-slim.
|
||||
# On the full bookworm image these are already installed (apt-get is a no-op).
|
||||
# Smoke workflows can opt out of distro upgrades to cut repeated CI time while
|
||||
# keeping the default runtime image behavior unchanged.
|
||||
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
if [ "${OPENCLAW_DOCKER_APT_UPGRADE}" != "0" ]; then \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y --no-install-recommends; \
|
||||
fi && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
ca-certificates procps hostname curl git lsof openssl && \
|
||||
update-ca-certificates
|
||||
procps hostname curl git lsof openssl
|
||||
|
||||
RUN chown node:node /app
|
||||
|
||||
COPY --from=runtime-assets --chown=node:node /app/dist ./dist
|
||||
COPY --from=runtime-assets --chown=node:node /app/node_modules ./node_modules
|
||||
COPY --from=runtime-assets --chown=node:node /app/package.json .
|
||||
COPY --from=runtime-assets --chown=node:node /app/patches ./patches
|
||||
COPY --from=runtime-assets --chown=node:node /app/openclaw.mjs .
|
||||
COPY --from=runtime-assets --chown=node:node /app/${OPENCLAW_BUNDLED_PLUGIN_DIR} ./${OPENCLAW_BUNDLED_PLUGIN_DIR}
|
||||
COPY --from=runtime-assets --chown=node:node /app/skills ./skills
|
||||
@@ -261,13 +258,6 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar
|
||||
RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
|
||||
&& chmod 755 /app/openclaw.mjs
|
||||
|
||||
# Pre-create the default state and runtime-deps dirs so first-run Docker named
|
||||
# volumes mounted here inherit node ownership instead of root-owned state.
|
||||
RUN install -d -m 0700 -o node -g node /home/node/.openclaw && \
|
||||
install -d -m 0700 -o node -g node /var/lib/openclaw/plugin-runtime-deps && \
|
||||
stat -c '%U:%G %a' /home/node/.openclaw | grep -qx 'node:node 700' && \
|
||||
stat -c '%U:%G %a' /var/lib/openclaw/plugin-runtime-deps | grep -qx 'node:node 700'
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Security hardening: Run as non-root user
|
||||
|
||||
@@ -7,6 +7,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get upgrade -y --no-install-recommends \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
|
||||
@@ -7,6 +7,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get upgrade -y --no-install-recommends \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash \
|
||||
ca-certificates \
|
||||
|
||||
@@ -24,6 +24,7 @@ ENV PATH=${BUN_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/bin:${BREW_INSTALL_DIR}/sbin
|
||||
RUN --mount=type=cache,id=openclaw-sandbox-common-apt-cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,id=openclaw-sandbox-common-apt-lists,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get upgrade -y --no-install-recommends \
|
||||
&& apt-get install -y --no-install-recommends ${PACKAGES}
|
||||
|
||||
RUN if [ "${INSTALL_PNPM}" = "1" ]; then npm install -g pnpm; fi
|
||||
|
||||
@@ -64,7 +64,6 @@ These are frequently reported but are typically closed with no code change:
|
||||
- Reports that only show differences in heuristic detection/parity (for example obfuscation-pattern detection on one exec path but not another, such as `node.invoke -> system.run` parity gaps) without demonstrating bypass of auth, approvals, allowlist enforcement, sandboxing, or other documented trust boundaries.
|
||||
- Reports that only show an ACP tool can indirectly execute, mutate, orchestrate sessions, or reach another tool/runtime without demonstrating bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. ACP silent approval is intentionally limited to narrow readonly classes; parity-only indirect-command findings are hardening, not vulnerabilities.
|
||||
- Reports that only show untrusted media bytes reaching a maintained native decoder dependency (for example Sharp/libvips/libheif) without proving the shipped dependency version is vulnerable and demonstrating crash, memory corruption, data exposure, or a boundary bypass through OpenClaw. JavaScript header sniffing and image dimension fast-paths are preflight/UX checks, not the security boundary for native decoder correctness.
|
||||
- Reports whose only impact is transient extra memory, CPU, or allocation work from decoding, base64 expansion, media transcoding, serialization, or other format conversion after the input was already accepted under OpenClaw's configured size/trust limits, including base64 decode-before-size-estimate findings. These are performance issues, not vulnerabilities, unless the report demonstrates unauthenticated amplification, bypass of configured limits, crash/process termination, persistent resource exhaustion, data exposure, or another documented boundary bypass.
|
||||
- ReDoS/DoS claims that require trusted operator configuration input (for example catastrophic regex in `sessionFilter` or `logging.redactPatterns`) without a trust-boundary bypass.
|
||||
- Archive/install extraction claims that require pre-existing local filesystem priming in trusted state (for example planting symlink/hardlink aliases under destination directories such as skills/tools paths) without showing an untrusted path that can create/control that primitive.
|
||||
- Reports that depend on replacing or rewriting an already-approved executable path on a trusted host (same-path inode/content swap) without showing an untrusted path to perform that write.
|
||||
@@ -76,7 +75,6 @@ These are frequently reported but are typically closed with no code change:
|
||||
- Claims that Microsoft Teams `fileConsent/invoke` `uploadInfo.uploadUrl` is attacker-controlled without demonstrating one of: auth boundary bypass, a real authenticated Teams/Bot Framework event carrying attacker-chosen URL, or compromise of the Microsoft/Bot trust path.
|
||||
- Scanner-only claims against stale/nonexistent paths, or claims without a working repro.
|
||||
- Reports that restate an already-fixed issue against later released versions without showing the vulnerable path still exists in the shipped tag or published artifact for that later version.
|
||||
- SSRF reports against the operator-managed HTTP/WebSocket proxy-routing feature whose only claim is that ordinary process-local HTTP clients (`fetch`, `node:http`, `node:https`, WebSocket clients, axios/got/node-fetch-style clients) can reach an internal, metadata, private, or otherwise sensitive destination when proxy routing is disabled, missing, or the operator-managed proxy policy allows it. For this feature, OpenClaw provides fail-closed proxy routing when enabled; the external proxy's destination policy is operator infrastructure, not an OpenClaw-controlled security boundary. See [Network proxy](https://docs.openclaw.ai/security/network-proxy).
|
||||
|
||||
### Duplicate Report Handling
|
||||
|
||||
@@ -150,11 +148,9 @@ Plugins/extensions are part of OpenClaw's trusted computing base for a gateway.
|
||||
- Reports whose only claim is that an ACP-exposed tool can indirectly execute commands, mutate host state, or reach another privileged tool/runtime without demonstrating a bypass of ACP prompt/approval, allowlist enforcement, sandboxing, or another documented trust boundary. These are hardening-only findings, not vulnerabilities.
|
||||
- Reports whose only claim is that exec approvals do not semantically model every interpreter/runtime loader form, subcommand, flag combination, package script, or transitive module/config import. Exec approvals bind exact request context and best-effort direct local file operands; they are not a complete semantic model of everything a runtime may load.
|
||||
- Reports whose only claim is parser reachability in an up-to-date maintained dependency without showing that the exact shipped dependency build is vulnerable. We keep native media dependencies current; dependency exposure alone is not a vulnerability.
|
||||
- Reports whose only claim is resource overhead from decode/encode, base64 expansion, media transcoding, serialization, or format-conversion order after input has already passed the applicable configured acceptance limits, including base64 decode-before-size-estimate findings. These are performance-only and should be ignored for GHSA triage unless the report demonstrates unauthenticated amplification, limit bypass, crash/process termination, persistent exhaustion, data exposure, or another documented boundary bypass.
|
||||
- Exposed secrets that are third-party/user-controlled credentials (not OpenClaw-owned and not granting access to OpenClaw-operated infrastructure/services) without demonstrated OpenClaw impact
|
||||
- Reports whose only claim is host-side exec when sandbox runtime is disabled/unavailable (documented default behavior in the trusted-operator model), without a boundary bypass.
|
||||
- Reports whose only claim is that a platform-provided upload destination URL is untrusted (for example Microsoft Teams `fileConsent/invoke` `uploadInfo.uploadUrl`) without proving attacker control in an authenticated production flow.
|
||||
- SSRF reports limited to the operator-managed HTTP/WebSocket proxy-routing feature where the demonstrated mitigation is to enable/configure `proxy.enabled` with a filtering `proxy.proxyUrl`/`OPENCLAW_PROXY_URL`, or where impact depends on a permissive/misconfigured operator proxy. This only covers normal process-local HTTP(S)/WebSocket egress (`fetch`, Node HTTP(S), and similar JavaScript clients); non-HTTP egress and other features are assessed separately. See [Network proxy](https://docs.openclaw.ai/security/network-proxy).
|
||||
|
||||
## Deployment Assumptions
|
||||
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
{
|
||||
"originHash" : "e6910acc97de62dc423c0a391985c1c2f28207951e356081539abde41f9ffc72",
|
||||
"originHash" : "24a723309d7a0039d3df3051106f77ac1ed7068a02508e3a6804e41d757e6c72",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "commander",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/steipete/Commander.git",
|
||||
"state" : {
|
||||
"revision" : "ae2ce746b386ff94b26648cfe5625cfa8d02639b",
|
||||
"version" : "0.2.2"
|
||||
"revision" : "9e349575c8e3c6745e81fe19e5bb5efa01b078ce",
|
||||
"version" : "0.2.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "elevenlabskit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/steipete/ElevenLabsKit",
|
||||
"state" : {
|
||||
"revision" : "7e3c948d8340abe3977014f3de020edf221e9269",
|
||||
"version" : "0.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-concurrency-extras",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
|
||||
"state" : {
|
||||
"revision" : "5a3825302b1a0d744183200915a47b508c828e6f",
|
||||
"version" : "1.3.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -27,6 +45,24 @@
|
||||
"revision" : "399f76dcd91e4c688ca2301fa24a8cc6d9927211",
|
||||
"version" : "0.99.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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
|
||||
|
||||
@@ -13,7 +13,7 @@ let package = Package(
|
||||
.executable(name: "swabble", targets: ["SwabbleCLI"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/steipete/Commander.git", exact: "0.2.2"),
|
||||
.package(url: "https://github.com/steipete/Commander.git", exact: "0.2.1"),
|
||||
.package(url: "https://github.com/apple/swift-testing", from: "0.99.0"),
|
||||
],
|
||||
targets: [
|
||||
@@ -43,6 +43,7 @@ let package = Package(
|
||||
],
|
||||
swiftSettings: [
|
||||
.enableUpcomingFeature("StrictConcurrency"),
|
||||
.enableExperimentalFeature("SwiftTesting"),
|
||||
]),
|
||||
.testTarget(
|
||||
name: "swabbleTests",
|
||||
|
||||
@@ -45,15 +45,6 @@ extension AttributedString {
|
||||
}
|
||||
|
||||
return ranges.compactMap { range in
|
||||
guard #available(macOS 26.0, iOS 26.0, *) else {
|
||||
return AttributedString(self[range].characters)
|
||||
}
|
||||
return self.sentenceWithAudioTimeRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 26.0, iOS 26.0, *)
|
||||
private func sentenceWithAudioTimeRange(_ range: Range<AttributedString.Index>) -> AttributedString? {
|
||||
let audioTimeRanges = self[range].runs.filter {
|
||||
!String(self[$0.range].characters)
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
@@ -66,5 +57,6 @@ extension AttributedString {
|
||||
start: start,
|
||||
end: end)
|
||||
return AttributedString(self[range].characters, attributes: attributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,35 +17,29 @@ public enum OutputFormat: String {
|
||||
case .txt:
|
||||
return String(transcript.characters)
|
||||
case .srt:
|
||||
guard #available(macOS 26.0, iOS 26.0, *) else { return "" }
|
||||
return self.srtText(for: transcript, maxLength: maxLength)
|
||||
func format(_ timeInterval: TimeInterval) -> String {
|
||||
let ms = Int(timeInterval.truncatingRemainder(dividingBy: 1) * 1000)
|
||||
let s = Int(timeInterval) % 60
|
||||
let m = (Int(timeInterval) / 60) % 60
|
||||
let h = Int(timeInterval) / 60 / 60
|
||||
return String(format: "%0.2d:%0.2d:%0.2d,%0.3d", h, m, s, ms)
|
||||
}
|
||||
|
||||
return transcript.sentences(maxLength: maxLength).compactMap { (sentence: AttributedString) -> (
|
||||
CMTimeRange,
|
||||
String)? in
|
||||
guard let timeRange = sentence.audioTimeRange else { return nil }
|
||||
return (timeRange, String(sentence.characters))
|
||||
}.enumerated().map { index, run in
|
||||
let (timeRange, text) = run
|
||||
return """
|
||||
|
||||
\(index + 1)
|
||||
\(format(timeRange.start.seconds)) --> \(format(timeRange.end.seconds))
|
||||
\(text.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
|
||||
"""
|
||||
}.joined().trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 26.0, iOS 26.0, *)
|
||||
private func srtText(for transcript: AttributedString, maxLength: Int) -> String {
|
||||
func format(_ timeInterval: TimeInterval) -> String {
|
||||
let ms = Int(timeInterval.truncatingRemainder(dividingBy: 1) * 1000)
|
||||
let s = Int(timeInterval) % 60
|
||||
let m = (Int(timeInterval) / 60) % 60
|
||||
let h = Int(timeInterval) / 60 / 60
|
||||
return String(format: "%0.2d:%0.2d:%0.2d,%0.3d", h, m, s, ms)
|
||||
}
|
||||
|
||||
return transcript.sentences(maxLength: maxLength).compactMap { (sentence: AttributedString) -> (
|
||||
CMTimeRange,
|
||||
String)? in
|
||||
guard let timeRange = sentence.audioTimeRange else { return nil }
|
||||
return (timeRange, String(sentence.characters))
|
||||
}.enumerated().map { index, run in
|
||||
let (timeRange, text) = run
|
||||
return """
|
||||
|
||||
\(index + 1)
|
||||
\(format(timeRange.start.seconds)) --> \(format(timeRange.end.seconds))
|
||||
\(text.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
|
||||
"""
|
||||
}.joined().trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ public struct WakeWordSegment: Sendable, Equatable {
|
||||
self.range = range
|
||||
}
|
||||
|
||||
public var end: TimeInterval {
|
||||
self.start + self.duration
|
||||
}
|
||||
public var end: TimeInterval { start + duration }
|
||||
}
|
||||
|
||||
public struct WakeWordGateConfig: Sendable, Equatable {
|
||||
@@ -26,8 +24,7 @@ public struct WakeWordGateConfig: Sendable, Equatable {
|
||||
public init(
|
||||
triggers: [String],
|
||||
minPostTriggerGap: TimeInterval = 0.45,
|
||||
minCommandLength: Int = 1)
|
||||
{
|
||||
minCommandLength: Int = 1) {
|
||||
self.triggers = triggers
|
||||
self.minPostTriggerGap = minPostTriggerGap
|
||||
self.minCommandLength = minCommandLength
|
||||
@@ -38,18 +35,11 @@ public struct WakeWordGateMatch: Sendable, Equatable {
|
||||
public let triggerEndTime: TimeInterval
|
||||
public let postGap: TimeInterval
|
||||
public let command: String
|
||||
public let trigger: String?
|
||||
|
||||
public init(
|
||||
triggerEndTime: TimeInterval,
|
||||
postGap: TimeInterval,
|
||||
command: String,
|
||||
trigger: String? = nil)
|
||||
{
|
||||
public init(triggerEndTime: TimeInterval, postGap: TimeInterval, command: String) {
|
||||
self.triggerEndTime = triggerEndTime
|
||||
self.postGap = postGap
|
||||
self.command = command
|
||||
self.trigger = trigger
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +53,13 @@ public enum WakeWordGate {
|
||||
}
|
||||
|
||||
private struct TriggerTokens {
|
||||
let source: String
|
||||
let tokens: [String]
|
||||
}
|
||||
|
||||
private struct MatchCandidate {
|
||||
let index: Int
|
||||
let endIndex: Int
|
||||
let tokenCount: Int
|
||||
let triggerEnd: TimeInterval
|
||||
let gap: TimeInterval
|
||||
let trigger: String
|
||||
}
|
||||
|
||||
public static func match(
|
||||
@@ -81,10 +67,10 @@ public enum WakeWordGate {
|
||||
segments: [WakeWordSegment],
|
||||
config: WakeWordGateConfig)
|
||||
-> WakeWordGateMatch? {
|
||||
let triggerTokens = self.normalizeTriggers(config.triggers)
|
||||
let triggerTokens = normalizeTriggers(config.triggers)
|
||||
guard !triggerTokens.isEmpty else { return nil }
|
||||
|
||||
let tokens = self.normalizeSegments(segments)
|
||||
let tokens = normalizeSegments(segments)
|
||||
guard !tokens.isEmpty else { return nil }
|
||||
|
||||
var best: MatchCandidate?
|
||||
@@ -101,31 +87,17 @@ public enum WakeWordGate {
|
||||
let gap = nextToken.start - triggerEnd
|
||||
if gap < config.minPostTriggerGap { continue }
|
||||
|
||||
let endIndex = i + count - 1
|
||||
if let best {
|
||||
if endIndex < best.endIndex { continue }
|
||||
if endIndex == best.endIndex, count <= best.tokenCount { continue }
|
||||
}
|
||||
if let best, i <= best.index { continue }
|
||||
|
||||
best = MatchCandidate(
|
||||
index: i,
|
||||
endIndex: endIndex,
|
||||
tokenCount: count,
|
||||
triggerEnd: triggerEnd,
|
||||
gap: gap,
|
||||
trigger: trigger.source)
|
||||
best = MatchCandidate(index: i, triggerEnd: triggerEnd, gap: gap)
|
||||
}
|
||||
}
|
||||
|
||||
guard let best else { return nil }
|
||||
let command = self.commandText(transcript: transcript, segments: segments, triggerEndTime: best.triggerEnd)
|
||||
let command = commandText(transcript: transcript, segments: segments, triggerEndTime: best.triggerEnd)
|
||||
.trimmingCharacters(in: Self.whitespaceAndPunctuation)
|
||||
guard command.count >= config.minCommandLength else { return nil }
|
||||
return WakeWordGateMatch(
|
||||
triggerEndTime: best.triggerEnd,
|
||||
postGap: best.gap,
|
||||
command: command,
|
||||
trigger: best.trigger)
|
||||
return WakeWordGateMatch(triggerEndTime: best.triggerEnd, postGap: best.gap, command: command)
|
||||
}
|
||||
|
||||
public static func commandText(
|
||||
@@ -148,7 +120,7 @@ public enum WakeWordGate {
|
||||
guard !text.isEmpty else { return false }
|
||||
let normalized = text.lowercased()
|
||||
for trigger in triggers {
|
||||
let token = trigger.trimmingCharacters(in: self.whitespaceAndPunctuation).lowercased()
|
||||
let token = trigger.trimmingCharacters(in: whitespaceAndPunctuation).lowercased()
|
||||
if token.isEmpty { continue }
|
||||
if normalized.contains(token) { return true }
|
||||
}
|
||||
@@ -158,11 +130,11 @@ public enum WakeWordGate {
|
||||
public static func stripWake(text: String, triggers: [String]) -> String {
|
||||
var out = text
|
||||
for trigger in triggers {
|
||||
let token = trigger.trimmingCharacters(in: self.whitespaceAndPunctuation)
|
||||
let token = trigger.trimmingCharacters(in: whitespaceAndPunctuation)
|
||||
guard !token.isEmpty else { continue }
|
||||
out = out.replacingOccurrences(of: token, with: "", options: [.caseInsensitive])
|
||||
}
|
||||
return out.trimmingCharacters(in: self.whitespaceAndPunctuation)
|
||||
return out.trimmingCharacters(in: whitespaceAndPunctuation)
|
||||
}
|
||||
|
||||
private static func normalizeTriggers(_ triggers: [String]) -> [TriggerTokens] {
|
||||
@@ -170,17 +142,17 @@ public enum WakeWordGate {
|
||||
for trigger in triggers {
|
||||
let tokens = trigger
|
||||
.split(whereSeparator: { $0.isWhitespace })
|
||||
.map { self.normalizeToken(String($0)) }
|
||||
.map { normalizeToken(String($0)) }
|
||||
.filter { !$0.isEmpty }
|
||||
if tokens.isEmpty { continue }
|
||||
output.append(TriggerTokens(source: tokens.joined(separator: " "), tokens: tokens))
|
||||
output.append(TriggerTokens(tokens: tokens))
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private static func normalizeSegments(_ segments: [WakeWordSegment]) -> [Token] {
|
||||
segments.compactMap { segment in
|
||||
let normalized = self.normalizeToken(segment.text)
|
||||
let normalized = normalizeToken(segment.text)
|
||||
guard !normalized.isEmpty else { return nil }
|
||||
return Token(
|
||||
normalized: normalized,
|
||||
@@ -193,7 +165,7 @@ public enum WakeWordGate {
|
||||
|
||||
private static func normalizeToken(_ token: String) -> String {
|
||||
token
|
||||
.trimmingCharacters(in: self.whitespaceAndPunctuation)
|
||||
.trimmingCharacters(in: whitespaceAndPunctuation)
|
||||
.lowercased()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import Speech
|
||||
import Swabble
|
||||
|
||||
@MainActor
|
||||
@available(macOS 26.0, *)
|
||||
struct TranscribeCommand: ParsableCommand {
|
||||
@Argument(help: "Path to audio/video file") var inputFile: String = ""
|
||||
@Option(name: .long("locale"), help: "Locale identifier", parsing: .singleValue) var locale: String = Locale.current
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import Foundation
|
||||
import SwabbleKit
|
||||
import XCTest
|
||||
import Testing
|
||||
|
||||
final class WakeWordGateTests: XCTestCase {
|
||||
func testMatchRequiresGapAfterTrigger() {
|
||||
@Suite struct WakeWordGateTests {
|
||||
@Test func matchRequiresGapAfterTrigger() {
|
||||
let transcript = "hey clawd do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
@@ -14,10 +14,10 @@ final class WakeWordGateTests: XCTestCase {
|
||||
("thing", 0.5, 0.1),
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["clawd"], minPostTriggerGap: 0.3)
|
||||
XCTAssertNil(WakeWordGate.match(transcript: transcript, segments: segments, config: config))
|
||||
#expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config) == nil)
|
||||
}
|
||||
|
||||
func testMatchAllowsGapAndExtractsCommand() {
|
||||
@Test func matchAllowsGapAndExtractsCommand() {
|
||||
let transcript = "hey clawd do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
@@ -29,10 +29,10 @@ final class WakeWordGateTests: XCTestCase {
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["clawd"], minPostTriggerGap: 0.3)
|
||||
let match = WakeWordGate.match(transcript: transcript, segments: segments, config: config)
|
||||
XCTAssertEqual(match?.command, "do thing")
|
||||
#expect(match?.command == "do thing")
|
||||
}
|
||||
|
||||
func testMatchHandlesMultiWordTriggers() {
|
||||
@Test func matchHandlesMultiWordTriggers() {
|
||||
let transcript = "hey clawd do it"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
@@ -44,25 +44,10 @@ final class WakeWordGateTests: XCTestCase {
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["hey clawd"], minPostTriggerGap: 0.3)
|
||||
let match = WakeWordGate.match(transcript: transcript, segments: segments, config: config)
|
||||
XCTAssertEqual(match?.command, "do it")
|
||||
#expect(match?.command == "do it")
|
||||
}
|
||||
|
||||
func testMatchPrefersMostSpecificTriggerWhenOverlapping() {
|
||||
let transcript = "hey clawd do it"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("clawd", 0.2, 0.1),
|
||||
("do", 0.8, 0.1),
|
||||
("it", 1.0, 0.1),
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["clawd", "hey clawd"], minPostTriggerGap: 0.3)
|
||||
let match = WakeWordGate.match(transcript: transcript, segments: segments, config: config)
|
||||
XCTAssertEqual(match?.trigger, "hey clawd")
|
||||
}
|
||||
|
||||
func testCommandTextHandlesForeignRangeIndices() {
|
||||
@Test func commandTextHandlesForeignRangeIndices() {
|
||||
let transcript = "hey clawd do thing"
|
||||
let other = "do thing"
|
||||
let foreignRange = other.range(of: "do")
|
||||
@@ -78,7 +63,7 @@ final class WakeWordGateTests: XCTestCase {
|
||||
segments: segments,
|
||||
triggerEndTime: 0.3)
|
||||
|
||||
XCTAssertEqual(command, "do thing")
|
||||
#expect(command == "do thing")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Swabble
|
||||
import XCTest
|
||||
|
||||
final class ConfigTests: XCTestCase {
|
||||
func testConfigRoundTrip() throws {
|
||||
var cfg = SwabbleConfig()
|
||||
cfg.wake.word = "robot"
|
||||
let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".json")
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
@Test
|
||||
func configRoundTrip() throws {
|
||||
var cfg = SwabbleConfig()
|
||||
cfg.wake.word = "robot"
|
||||
let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".json")
|
||||
defer { try? FileManager.default.removeItem(at: url) }
|
||||
|
||||
try ConfigLoader.save(cfg, at: url)
|
||||
let loaded = try ConfigLoader.load(at: url)
|
||||
XCTAssertEqual(loaded.wake.word, "robot")
|
||||
XCTAssertTrue(loaded.hook.prefix.contains("Voice swabble"))
|
||||
}
|
||||
try ConfigLoader.save(cfg, at: url)
|
||||
let loaded = try ConfigLoader.load(at: url)
|
||||
#expect(loaded.wake.word == "robot")
|
||||
#expect(loaded.hook.prefix.contains("Voice swabble"))
|
||||
}
|
||||
|
||||
func testConfigMissingThrows() {
|
||||
XCTAssertThrowsError(
|
||||
try ConfigLoader.load(at: FileManager.default.temporaryDirectory.appendingPathComponent("nope.json")))
|
||||
@Test
|
||||
func configMissingThrows() {
|
||||
#expect(throws: ConfigError.missingConfig) {
|
||||
_ = try ConfigLoader.load(at: FileManager.default.temporaryDirectory.appendingPathComponent("nope.json"))
|
||||
}
|
||||
}
|
||||
|
||||
952
appcast.xml
952
appcast.xml
@@ -2,645 +2,6 @@
|
||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||
<channel>
|
||||
<title>OpenClaw</title>
|
||||
<item>
|
||||
<title>2026.4.26</title>
|
||||
<pubDate>Tue, 28 Apr 2026 02:40:27 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026042690</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.4.26</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.4.26</h2>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C <code>stream_messages</code> streaming with a <code>StreamingController</code> lifecycle manager, unified <code>sendMedia</code> with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via <code>createEngineAdapters()</code>. (#70624) Thanks @cxyhhhhh.</li>
|
||||
<li>Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (<code>openclaw-plugin-yuanbao</code>) in the official channel catalog, contract suites, and community plugin docs, with a new <code>docs/channels/yuanbao.md</code> quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.</li>
|
||||
<li>Control UI/Talk: add a generic browser realtime transport contract, Google Live browser Talk sessions with constrained ephemeral tokens, and a Gateway relay for backend-only realtime voice plugins. Thanks @VACInc.</li>
|
||||
<li>CLI/models: route provider-filtered model listing through an explicit source plan so user config, installed manifest rows, Provider Index previews, and scoped runtime fallbacks keep a stable authority order without adding another catalog cache. Thanks @shakkernerd.</li>
|
||||
<li>Providers: add Cerebras as a bundled plugin with onboarding, static model catalog, docs, and manifest-owned endpoint metadata.</li>
|
||||
<li>Memory/OpenAI-compatible: add optional <code>memorySearch.inputType</code>, <code>queryInputType</code>, and <code>documentInputType</code> config for asymmetric embedding endpoints, including direct query embeddings and provider batch indexing. Carries forward #63313 and #60727. Thanks @HOYALIM and @prospect1314521.</li>
|
||||
<li>Ollama/memory: add model-specific retrieval query prefixes for <code>nomic-embed-text</code>, <code>qwen3-embedding</code>, and <code>mxbai-embed-large</code> memory-search queries while leaving document batches unchanged. Carries forward #45013. Thanks @laolin5564.</li>
|
||||
<li>Plugins/providers: move pre-runtime model-id normalization, endpoint host metadata, OpenAI-compatible request-family hints, model-catalog aliases/suppressions, OpenAI stale Spark suppression, and reusable startup metadata snapshots into plugin manifests so core no longer carries bundled-provider routing tables or repeated manifest rebuilds. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/config: deprecate direct plugin config load/write helpers in favor of passed runtime snapshots plus transactional mutation helpers with explicit restart follow-up policy, scanner guardrails, runtime warnings, and revision-based cache invalidation.</li>
|
||||
<li>Plugins/install: allow <code>OPENCLAW_PLUGIN_STAGE_DIR</code> to contain layered runtime-dependency roots, resolving read-only preinstalled deps before installing missing deps into the final writable root. Fixes #72396. Thanks @liorb-mountapps.</li>
|
||||
<li>Control UI: add a raw config pending-changes diff panel that parses JSON5, redacts sensitive values until reveal, and avoids fake raw-edit callbacks when opening the panel. Refs #39831; supersedes #48621 and #46654. Thanks @JiajunBernoulli and @BunsDev.</li>
|
||||
<li>Control UI: polish the quick settings dashboard grid so common cards align across desktop, tablet, and mobile layouts without wasting horizontal space. Thanks @BunsDev.</li>
|
||||
<li>Matrix/E2EE: add <code>openclaw matrix encryption setup</code> to enable Matrix encryption, bootstrap recovery, and print verification status from one setup flow. Thanks @gumadeiras.</li>
|
||||
<li>Agents/compaction: add an opt-in <code>agents.defaults.compaction.maxActiveTranscriptBytes</code> preflight trigger that runs normal local compaction when the active JSONL grows too large, requiring transcript rotation so successful compaction moves future turns onto a smaller successor file instead of raw byte-splitting history. Thanks @vincentkoc.</li>
|
||||
<li>CLI/migration: add <code>openclaw migrate</code> with plan, dry-run, JSON, pre-migration backup, onboarding detection, archive-only reports, a Claude Code/Desktop importer, and a Hermes importer for configuration, memory/plugin hints, model providers, MCP servers, skills, commands, and supported credentials. Thanks @vincentkoc and @NousResearch.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Agents/LSP: terminate bundled stdio LSP process trees during runtime disposal and Gateway shutdown, so nested children such as <code>tsserver</code> do not survive stop or restart. Fixes #72357. Thanks @ai-hpc and @bittoby.</li>
|
||||
<li>Gateway/device tokens: stop echoing rotated bearer tokens from shared/admin <code>device.token.rotate</code> responses while preserving the same-device token handoff needed by token-only clients before reconnect. (#66773) Thanks @MoerAI.</li>
|
||||
<li>Control UI/Talk: keep Google Live browser sessions on the WebSocket transport instead of falling back to WebRTC, validate browser Google Live WebSocket endpoints, cap Gateway relay sessions per browser connection, and remove stale browser-native voice buttons that did not use the configured Talk/TTS provider. Thanks @BunsDev.</li>
|
||||
<li>Gateway/startup: reuse config snapshot plugin manifests for startup auto-enable, config validation, and plugin bootstrap planning, including authored source config and disabled setup-probe handling, so restrictive allowlists avoid duplicate manifest/config passes during boot. Thanks @shakkernerd.</li>
|
||||
<li>Agents/subagents: enforce <code>subagents.allowAgents</code> for explicit same-agent <code>sessions_spawn(agentId=...)</code> calls instead of auto-allowing requester self-targets. Fixes #72827. Thanks @oiGaDio.</li>
|
||||
<li>ACP/sessions_spawn: let explicit <code>sessions_spawn(runtime="acp")</code> bootstrap turns run while <code>acp.dispatch.enabled=false</code> still blocks automatic ACP thread dispatch. Fixes #63591. Thanks @moeedahmed.</li>
|
||||
<li>CLI/update: install npm global updates into a verified temporary prefix before swapping the package tree into place, preventing mixed old/new installs and stale packaged files from breaking <code>openclaw update</code> verification. Thanks @shakkernerd.</li>
|
||||
<li>Gateway: skip CLI startup self-respawn for foreground gateway runs so low-memory Linux/Node 24 hosts start through the same path as direct <code>dist/index.js</code> without hanging before logs. Fixes #72720. Thanks @sign-2025.</li>
|
||||
<li>Google Meet: route local Chrome joins through OpenClaw browser control, grant Meet media permissions, pin local Chrome audio defaults to <code>BlackHole 2ch</code>, and use the configured OpenClaw browser profile so joined agents no longer show <code>Permission needed</code> or use raw/default Chrome state. Thanks @DougButdorf and @oromeis.</li>
|
||||
<li>Plugins/discovery: follow symlinked plugin directories in global and workspace plugin roots while keeping broken links ignored and existing package safety checks in place. Fixes #36754; carries forward #72695 and #63206. Thanks @Quackstro, @ming1523, and @xsfX20.</li>
|
||||
<li>Plugins/install: skip test files and directories during install security scans while still force-scanning declared runtime entrypoints, so packaged test mocks no longer block plugin installs. Fixes #66840; carries forward #67050. Thanks @saurabhjain1592 and @Magicray1217.</li>
|
||||
<li>Plugins/install: allow exact package-manager peer links back to the trusted OpenClaw host package during install security scans while continuing to block spoofed or nested escaping <code>node_modules</code> symlinks. Carries forward #70819. Thanks @fgabelmannjr.</li>
|
||||
<li>Plugins/install: resolve plugin install destinations from the active profile state dir across CLI, ClawHub, marketplace, local path, and channel setup installs, so <code>openclaw --profile <name> plugins install ...</code> no longer writes into the default profile. Fixes #69960; carries forward #69971. Thanks @FrancisLyman and @Sanjays2402.</li>
|
||||
<li>Plugins/registry: suppress duplicate-plugin startup warnings when a tracked npm-installed plugin intentionally overrides the bundled plugin with the same id. Carries forward #48673. Thanks @abdushsk.</li>
|
||||
<li>Plugins/startup: reuse canonical realpath lookups throughout each plugin discovery pass, including package and manifest boundary checks, so Windows npm-global startups no longer repeat expensive path resolution for the same plugin roots. Fixes #65733. Thanks @welfo-beo.</li>
|
||||
<li>Gateway/proxy: pass <code>ALL_PROXY</code> / <code>all_proxy</code> into the global Undici env-proxy dispatcher and provider proxy-fetch helper while keeping SSRF trusted-proxy auto-upgrade on <code>HTTP_PROXY</code> / <code>HTTPS_PROXY</code> only, so gateway/provider calls honor all-proxy setups without weakening guarded fetches. Fixes #43821; carries forward #43919. Thanks @RickyTong1.</li>
|
||||
<li>Reply/link understanding: keep media and link preprocessing on stable runtime entrypoints and continue with raw message content if optional enrichment fails, so URL-bearing messages are no longer dropped after stale runtime chunk upgrades. Fixes #68466. Thanks @songshikang0111.</li>
|
||||
<li>Discord: persist routed model-picker overrides when the hidden <code>/model</code> dispatch succeeds but the bound thread session store is still stale, including LM Studio suffixed model ids. Carries forward #61473. Thanks @Nanako0129.</li>
|
||||
<li>Nodes/CLI: add <code>openclaw nodes remove --node <id|name|ip></code> and <code>node.pair.remove</code> so stale gateway-owned node pairing records can be cleaned without hand-editing state files.</li>
|
||||
<li>Gateway: include the connecting client and fresh presence version in the initial <code>hello-ok</code> snapshot, so clients no longer need a follow-up event before seeing themselves online.</li>
|
||||
<li>Docker: install the CA certificate bundle in the slim runtime image so HTTPS calls from containerized gateways no longer fail TLS setup after the <code>bookworm-slim</code> base switch. Fixes #72787. Thanks @ryuhaneul.</li>
|
||||
<li>Providers/OpenRouter: remove retired Hunter Alpha and Healer Alpha static catalog rows and disable proxy reasoning injection for stale Hunter Alpha configs, so replies are not hidden when OpenRouter returns answer text in reasoning fields. Fixes #43942. Thanks @EvanDataForge.</li>
|
||||
<li>Providers/reasoning: let Groq and LM Studio declare provider-native reasoning effort values, so Qwen thinking models receive <code>none</code>/<code>default</code> or <code>off</code>/<code>on</code> instead of OpenAI-only <code>low</code>/<code>medium</code> values. Fixes #32638. Thanks @Aqu1bp, @mgoulart, @Norpps, and @BSTail.</li>
|
||||
<li>Local models: default custom providers with only <code>baseUrl</code> to the Chat Completions adapter and trust loopback model requests automatically, so local OpenAI-compatible proxies receive <code>/v1/chat/completions</code> without timing out. Fixes #40024. Thanks @parachuteshe.</li>
|
||||
<li>Channels/message tool: surface Discord, Slack, and Mattermost <code>user:</code>/<code>channel:</code> target syntax in the shared message target schema and Discord ambiguity errors, so DM sends by numeric id stop burning retries before finding <code>user:<id></code>. Fixes #72401. Thanks @garyd9, @hclsys, and @praveen9354.</li>
|
||||
<li>Agents/tools: scope tool-loop detection history to the active run when available, so scheduled heartbeat cycles no longer inherit stale repeated-call counts from previous runs. Fixes #40144. Thanks @mattbrown319.</li>
|
||||
<li>Agents/subagents: preserve requester delivery for completion announces across different channel accounts, keep same-channel thread completions routed to the child thread, and fail closed instead of guessing a child binding when requester conversation signal is missing. Thanks @sfuminya and @suyua9.</li>
|
||||
<li>Agents/status: persist the post-compaction token estimate from auto-compaction when providers omit usage metadata, so <code>/status</code> and session lists keep showing fresh context usage after compaction. Fixes #67667; carries forward #72822. Thanks @Jimmy-xuzimo and @skylight-9.</li>
|
||||
<li>Control UI: show loading, reload, and retry states when a lazy dashboard panel cannot load after an upgrade, so the Logs tab no longer appears blank on stale browser bundles. Fixes #72450. Thanks @sobergou.</li>
|
||||
<li>Gateway/plugins: start the Gateway in degraded mode when a single plugin entry has invalid schema config, and let <code>openclaw doctor --fix</code> quarantine that plugin config instead of crash-looping every channel. Fixes #62976 and #70371. Thanks @Doraemon-Claw and @pksidekyk.</li>
|
||||
<li>Agents/plugins: skip malformed plugin tools with missing schema objects and report plugin diagnostics, so one broken tool no longer crashes Anthropic agent runs. Fixes #69423. Thanks @jmnickels.</li>
|
||||
<li>Agents/reasoning: recover fully wrapped unclosed <code><think></code> replies that would otherwise sanitize to empty text while keeping strict stripping for closed reasoning blocks and unclosed tails after visible text. Fixes #37696; supersedes #51915. Thanks @druide67 and @okuyam2y.</li>
|
||||
<li>Control UI/Gateway: bind WebChat handshakes to their active socket and reject post-close server registrations, so aborted connects no longer leave zombie clients or misleading duplicate WebSocket connection logs. Fixes #72753. Thanks @LumenFromTheFuture.</li>
|
||||
<li>Agents/fallback: split ambiguous provider failures into <code>empty_response</code>, <code>no_error_details</code>, and <code>unclassified</code>, and add flat fallback-step fields to structured fallback logs so primary-model failures stay visible when later fallbacks also fail. Fixes #71922; refs #71744. Thanks @andyk-ms and @nikolaykazakovvs-ux.</li>
|
||||
<li>Plugins/Windows: normalize Windows absolute paths before handing bundled plugin modules to Jiti, so Feishu/Lark message sending no longer fails with unsupported <code>c:</code> ESM loader URLs. Fixes #72783. Thanks @jackychen-png.</li>
|
||||
<li>CLI/doctor: run bundled plugin runtime-dependency repairs through the async npm installer with spinner/line progress and heartbeat updates, so long <code>openclaw doctor --fix</code> installs no longer look hung in TTY or piped output. Fixes #72775. Thanks @dfpalhano.</li>
|
||||
<li>Feishu/Windows: normalize bundled channel sidecar loads before Jiti evaluates them, so Feishu outbound sends no longer fail with raw <code>C:</code> ESM loader errors on Windows. Fixes #72783. Thanks @jackychen-png.</li>
|
||||
<li>Agents/tools: ignore volatile <code>exec</code> runtime metadata when comparing tool-loop outcomes, so enabled loop detection can stop repeated identical shell-command results instead of resetting on duration, PID, session, or cwd changes. Fixes #34574; supersedes #41502. Thanks @gucasbrg and @Zcg2021.</li>
|
||||
<li>Agents/fallback: classify internal live-session model switch conflicts as unknown fallback failures instead of provider overloads, preventing local vLLM endpoints from receiving misleading overloaded cooldowns. Refs #63229. Thanks @clawdia-lobster.</li>
|
||||
<li>Discord: let thread sessions inherit the parent channel's session-level <code>/model</code> override as a model-only fallback without enabling parent transcript inheritance. Fixes #72755. Thanks @solavrc.</li>
|
||||
<li>Gateway/plugins: skip stale configured channels whose matching plugin is no longer discoverable, point cleanup at <code>openclaw doctor --fix</code>, and keep unrelated channel typos fatal so one missing channel plugin no longer crash-loops the Gateway. Fixes #53311. Thanks @futhgar.</li>
|
||||
<li>Control UI: keep session-specific assistant identity loads authoritative after WebSocket connect, so non-main agent chat sessions do not show the main agent name in the header after bootstrap refreshes. Fixes #72776. Thanks @rockytian-top.</li>
|
||||
<li>Agents/Qwen: preserve exact custom <code>modelstudio</code> provider configs with foreign <code>api</code> owners so explicit OpenAI-compatible Model Studio endpoints no longer get normalized into the bundled Qwen plugin path. Fixes #64483. Thanks @FiredMosquito831.</li>
|
||||
<li>MCP/bundle-mcp: normalize CLI-native <code>type: "http"</code> MCP server entries to OpenClaw <code>transport: "streamable-http"</code> on save, repair existing configs with doctor, and keep embedded Pi from falling back to legacy SSE GET-first startup for those servers. Fixes #72757. Thanks @Studioscale.</li>
|
||||
<li>OpenCode: expose Anthropic Opus/Sonnet 4.x thinking levels for proxied Claude models, so <code>/think xhigh</code>, <code>/think adaptive</code>, and <code>/think max</code> validate consistently with the direct Anthropic provider. Fixes #72729. Thanks @haishmg and @aaajiao.</li>
|
||||
<li>Media-understanding/audio: migrate deprecated <code>{input}</code> placeholders in legacy <code>audio.transcription.command</code> configs to <code>{{MediaPath}}</code>, so custom audio transcribers no longer receive the literal placeholder after doctor repair. Fixes #72760. Thanks @krisfanue3-hash.</li>
|
||||
<li>Ollama/WSL2: warn when GPU-backed WSL2 installs combine CUDA visibility with an autostarting <code>ollama.service</code> using <code>Restart=always</code>, and document the systemd, <code>.wslconfig</code>, and keep-alive mitigation for crash loops. Carries forward #61022; fixes #61185. Thanks @yhyatt.</li>
|
||||
<li>Ollama/onboarding: de-dupe suggested bare local models against installed <code>:latest</code> tags and skip redundant pulls, so setup shows the installed model once and no longer says it is downloading an already available model. Fixes #68952. Thanks @tleyden.</li>
|
||||
<li>Memory-core/doctor: keep <code>doctor.memory.status</code> on the cached path by default and only run live embedding pings for explicit deep probes, preventing slow local embedding backends from blocking Gateway status checks. Fixes #71568. Thanks @apex-system.</li>
|
||||
<li>Memory/QMD: group same-source collections into one QMD search invocation when the installed QMD supports multiple <code>-c</code> filters, while keeping older QMD builds on the per-collection fallback. Fixes #72484; supersedes #72485 and #69583. Thanks @BsnizND and @zeroaltitude.</li>
|
||||
<li>Memory/QMD: accept QMD status vector-count variants such as <code>Vectors = 42</code>, <code>Vectors:42</code>, and <code>Vectors: 42 embedded</code>, so <code>memory status --deep</code> no longer reports embeddings unavailable for healthy QMD wrappers. Fixes #63652; carries forward #63678. Thanks @apoapostolov and @WarrenJones.</li>
|
||||
<li>Memory/QMD: skip QMD vector status probes and embedding maintenance in lexical <code>searchMode: "search"</code>, so BM25-only QMD setups on ARM do not trigger llama.cpp/Vulkan builds during status checks or embed cycles. Fixes #59234 and #67113. Thanks @PrinceOfEgypt, @Vksh07, @Snipe76, @NomLom, @t4r3e2q1-commits, and @dmak.</li>
|
||||
<li>Memory/QMD: report the live watcher dirty state in memory status, so changed QMD-backed memory files show as dirty until the queued sync finishes. Fixes #60244. Thanks @xinzf.</li>
|
||||
<li>Compaction: skip oversized pre-compaction checkpoint snapshots and prune duplicate long user turns from compaction input and rotated successor transcripts, preventing retry storms from being preserved across checkpoint cycles. Fixes #72780. Thanks @SweetSophia.</li>
|
||||
<li>Control UI/Cron: render cron job prompts and run summaries as sanitized markdown in the dashboard, with full-width block content, safer link clicks, and no duplicate error text when a failed run has no summary. Supersedes #48504. Thanks @garethdaine.</li>
|
||||
<li>Control UI/Gateway: preserve WebChat client version labels across localhost, 127.0.0.1, and IPv6 loopback aliases on the same port, avoiding misleading <code>vcontrol-ui</code> connection logs while investigating duplicate-message reports. Refs #72753 and #72742. Thanks @LumenFromTheFuture and @allesgutefy.</li>
|
||||
<li>Agents/reasoning: treat orphan closing reasoning tags with following answer text as a privacy boundary across delivery, history, streaming, and Control UI sanitizers so malformed local-model output cannot leak chain-of-thought text. Fixes #67092. Thanks @AnildoSilva.</li>
|
||||
<li>Memory-core: run one-shot memory CLI commands through transient builtin and QMD managers so <code>memory index</code>, <code>memory status --index</code>, and <code>memory search</code> no longer start long-lived file watchers that can hit macOS <code>EMFILE</code> limits. Fixes #59101; carries forward #49851. Thanks @mbear469210-coder and @maoyuanxue.</li>
|
||||
<li>Agents/ACP: ship the Claude ACP adapter with OpenClaw and require Claude result messages before idle can complete a prompt, preventing parent agents from waking early on long-running <code>sessions_spawn(runtime: "acp", agentId: "claude")</code> children. Fixes #72080. Thanks @siavash-saki and @iannwu.</li>
|
||||
<li>CLI/tasks: route <code>tasks --json</code>, <code>tasks list --json</code>, and <code>tasks audit --json</code> through a lean JSON path so read-only task inspection no longer loads unrelated plugin/runtime command graphs. Fixes #66238. Thanks @ChuckChambers.</li>
|
||||
<li>Memory-core: re-resolve the active runtime config whenever <code>memory_search</code> or <code>memory_get</code> executes, so provider changes made by <code>config.patch</code> stop leaving stale embedding backends behind in existing tool instances. Fixes #61098. Thanks @BradGroux and @Linux2010.</li>
|
||||
<li>WebChat: keep bare <code>/new</code> and <code>/reset</code> startup instructions out of visible chat history while preserving <code>/reset <note></code> as user-visible transcript text. Fixes #72369. Thanks @collynes and @haishmg.</li>
|
||||
<li>Tasks/memory: checkpoint and truncate SQLite WAL sidecars on a timer and before close for task, Task Flow, proxy capture, and builtin memory databases, bounding long-running gateway <code>*.sqlite-wal</code> growth. Fixes #72774. Thanks @dfpalhano.</li>
|
||||
<li>CLI/doctor: remove dangling channel config, heartbeat targets, and channel model overrides when stale plugin repair removes a missing channel plugin, preventing Gateway boot loops after failed plugin reinstalls. Fixes #65293. Thanks @yidecode.</li>
|
||||
<li>Control UI/Gateway: cache, coalesce, stale-refresh, and invalidate effective tool inventory on channel registry changes while reusing the gateway-bound plugin registry and avoiding model/auth discovery, so chat runs no longer stall Control UI requests on repeated plugin/model setup. Fixes #72365; supersedes #72558. Thanks @Gabiii2398 and @1yihui.</li>
|
||||
<li>Channels/setup: treat bundled channel plugins as already bundled during <code>channels add</code> and onboarding, enabling them without writing redundant <code>plugins.load.paths</code> entries or path install records. Fixes #72740. Thanks @iCodePoet.</li>
|
||||
<li>WhatsApp: honor gateway <code>HTTPS_PROXY</code> / <code>HTTP_PROXY</code> env vars for QR-login WebSocket connections, while respecting <code>NO_PROXY</code>, so proxied networks no longer fall back to direct <code>mmg.whatsapp.net</code> connections that time out with 408. Fixes #72547; supersedes #72692. Thanks @mebusw and @SymbolStar.</li>
|
||||
<li>Bonjour: default mDNS advertisements to the system hostname when it is DNS-safe, avoiding <code>openclaw.local</code> probing conflicts and Gateway restart loops on hosts such as <code>Lobster</code> or <code>ubuntu</code>. Fixes #72355 and #72689; supersedes #72694. Thanks @mscheuerlein-bot, @gcusms, @moyuwuhen601, @pavan987, @zml-0912, @hhq365, and @SymbolStar.</li>
|
||||
<li>Agents/OpenAI-compatible: retry replay-safe empty <code>stop</code> turns once for <code>openai-completions</code> endpoints, so transient empty local backend responses no longer surface as “Agent couldn't generate a response” when a continuation succeeds, and restore <code>openclaw agent --model</code> for one-shot CLI runs. Fixes #72751. Thanks @moooV252.</li>
|
||||
<li>Git hooks: skip ignored staged paths when formatting and restaging pre-commit files, so merge commits no longer abort when <code>.gitignore</code> newly ignores staged merged content. Fixes #72744. Thanks @100yenadmin.</li>
|
||||
<li>Memory-core/dreaming: add a supported <code>dreaming.model</code> knob for Dream Diary narrative subagents, wired through phase config and the existing plugin subagent model-override trust gate. Refs #65963. Thanks @esqandil and @mjamiv.</li>
|
||||
<li>Agents/Anthropic: remove trailing assistant prefill payloads when extended thinking is enabled, so Opus 4.7/Sonnet 4.6 requests do not fail Anthropic's user-final-turn validation. Fixes #72739. Thanks @superandylin.</li>
|
||||
<li>Agents/vLLM/Qwen: add plugin-owned Qwen thinking controls for vLLM chat-template kwargs and DashScope-style top-level <code>enable_thinking</code> flags, including preserved thinking for agent loops. Fixes #72329. Thanks @stavrostzagadouris.</li>
|
||||
<li>Memory-core/dreaming: treat request-scoped narrative fallback as expected, skip session cleanup when no subagent run was created, and remove duplicate phase-level cleanup so fallback no longer emits warning noise. Fixes #67152. Thanks @jsompis.</li>
|
||||
<li>Agents/exec: apply configured <code>tools.exec.timeoutSec</code> to background, <code>yieldMs</code>, and node <code>system.run</code> commands when no per-call timeout is set, preventing auto-backgrounded and remote node commands from running indefinitely. Fixes #67600; supersedes #67603. Thanks @dlmpx and @kagura-agent.</li>
|
||||
<li>Config/doctor: stop masking unknown-key validation diagnostics such as <code>agents.defaults.llm</code>, and have <code>openclaw doctor --fix</code> remove the retired <code>agents.defaults.llm</code> timeout block. Thanks @aidiffuser.</li>
|
||||
<li>CLI/startup: keep the built pre-dispatch CLI graph free of package-level imports and extend packaged CLI smoke coverage to onboard and doctor help paths, preventing missing runtime dependencies such as tslog from killing onboarding before repair code can run. Fixes #63024. Thanks @hu19940121.</li>
|
||||
<li>CLI/plugins: preserve unversioned ClawHub install specs so <code>plugins update</code> can follow newer ClawHub releases instead of pinning to the initially resolved version. Fixes #63010; supersedes #58426. Thanks @kangsen1234 and @robinspt.</li>
|
||||
<li>Memory-core/subagents: tag plugin-created subagent sessions with their plugin owner so dreaming narrative cleanup can delete its own ephemeral sessions without granting broad admin session deletion. Fixes #72712. Thanks @BSG2000.</li>
|
||||
<li>Gateway/models: move local-provider pricing opt-outs, OpenRouter/LiteLLM aliases, and proxy passthrough pricing lookup into plugin manifest metadata so core no longer carries extension-specific pricing tables.</li>
|
||||
<li>CLI/update: honor <code>OPENCLAW_NO_AUTO_UPDATE=1</code> as a gateway startup kill-switch for configured background package auto-updates, so operators can hold a deliberate downgrade during incident recovery without editing config first. Fixes #72715. Thanks @Xivi08.</li>
|
||||
<li>Agents/Claude CLI: force live-session launches to include <code>--output-format stream-json</code> whenever OpenClaw adds <code>--input-format stream-json</code>, so new Claude CLI sessions no longer fail immediately while reusable sessions keep working. Fixes #72206. Thanks @kwangwonkoh and @Xivi08.</li>
|
||||
<li>CLI/plugins: accept ClawHub plugin API wildcard ranges such as <code>*</code> without rejecting compatible plugin installs, while still requiring a valid runtime API version. Fixes #56446; supersedes #56466. Thanks @darconada and @claygeo.</li>
|
||||
<li>CLI/plugins: add an explicit <code>npm:<package></code> install prefix that skips ClawHub lookup for known npm packages while keeping bare package specs ClawHub-first. Fixes #55805; supersedes #54377. Thanks @Zeoy2020 and @vagusX.</li>
|
||||
<li>CLI/plugins: let config-gated bundled plugins install without persisting invalid placeholder config entries, so install/uninstall sweeps can cover plugins such as memory-lancedb before the user configures credentials. Thanks @vincentkoc.</li>
|
||||
<li>CLI/plugins: reject malformed ClawHub plugin specs with trailing <code>@</code> before registry lookup, so empty-version typos report as invalid specs instead of package-not-found errors. Fixes #56579; supersedes #56582. Thanks @Kansodata.</li>
|
||||
<li>Agents/sessions: acquire the session write lock only after cold bootstrap, plugin, and tool setup so fallback runs are not blocked by stalled pre-model startup work.</li>
|
||||
<li>Browser/plugins: auto-start the bundled browser plugin when root <code>browser</code> config is present, including restrictive plugin allowlists, and ignore stale persisted plugin registries whose package paths no longer exist.</li>
|
||||
<li>Browser: circuit-break repeated managed Chrome launch failures per profile so browser requests stop spawning Chromium indefinitely when CDP cannot start. Fixes #64271. Thanks @TheophilusChinomona.</li>
|
||||
<li>Gateway/models: skip external OpenRouter and LiteLLM pricing refreshes for local/self-hosted model endpoints so startup does not wait on remote pricing catalogs for local-only Ollama, vLLM, and compatible providers.</li>
|
||||
<li>CLI/plugins: stop security-blocked plugin installs from retrying as hook packs, so normal plugin packages report the scanner failure without a misleading "not a valid hook pack" follow-up. Fixes #61175; supersedes #64102. Thanks @KonsultDigital and @ziyincody.</li>
|
||||
<li>Agents/Anthropic: strip stale trailing assistant prefill turns from outbound replay so context-engine short circuits cannot send unsupported assistant-prefill payloads to provider APIs. Fixes #72556. Thanks @Veda-openclaw.</li>
|
||||
<li>Agents/Google: strip stale trailing assistant/model prefill turns from Gemini outbound replay so Google Generative AI requests end with a user turn or function response. Follow-up to #72556. Thanks @Veda-openclaw.</li>
|
||||
<li>Control UI/Dreaming: require explicit confirmation before applying restart-impacting Dreaming mode changes, with restart warning copy and loading feedback. Fixes #63804. (#63807) Thanks @bbddbb1.</li>
|
||||
<li>CLI/agent: mark Gateway-to-embedded fallback runs with <code>meta.transport: "embedded"</code> and <code>meta.fallbackFrom: "gateway"</code> in JSON output, and make the terminal diagnostic explicit so scripts and operators can distinguish fallback runs from Gateway runs. Fixes #71416. Thanks @amknight.</li>
|
||||
<li>Agents/tools: normalize <code>null</code> or missing tool-call arguments to <code>{}</code> for parameterless object schemas before Pi validation, so empty-argument tools run instead of failing argument validation. Fixes #72587. Thanks @amknight.</li>
|
||||
<li>Agents/subagents: clear active embedded-run state before terminal lifecycle events so post-completion cleanup no longer treats finished child runs as still active and skips archive or announcement bookkeeping. (#70187) Thanks @amknight.</li>
|
||||
<li>CLI/update: keep the automatic post-update completion refresh on the core-command tree so it no longer stages bundled plugin runtime deps before the Gateway restart path, avoiding <code>.24</code> update hangs and 1006 disconnect cascades. Fixes #72665. Thanks @sakalaboator and @He-Pin.</li>
|
||||
<li>Control UI: make explicit Reload Config actions discard stale local config edits while passive refreshes and failed-save recovery keep pending drafts intact. Fixes #40352; carries forward #40443. Thanks @realmikechong-dotcom.</li>
|
||||
<li>Agents/Bedrock: stop heartbeat runs from persisting blank user transcript turns and repair existing blank user text messages before replay, preventing AWS Bedrock <code>ContentBlock</code> blank-text validation failures. Fixes #72640 and #72622. Thanks @goldzulu.</li>
|
||||
<li>Agents/LM Studio: promote standalone bracketed local-model tool requests into registered tool calls and hide unsupported bracket blocks from visible replies, so MemPalace MCP lookups do not print raw <code>[tool]</code> JSON scaffolding in chat. Fixes #66178. Thanks @detroit357.</li>
|
||||
<li>Local models: warn when an assistant reply looks like a tool call but the provider emitted plain text instead of a structured tool invocation, making fake/non-executed tool calls visible in logs. Fixes #51332. Thanks @emilclaw.</li>
|
||||
<li>Local models: accept persisted non-secret local auth markers for private-LAN custom OpenAI-compatible providers, so LAN Ollama configs no longer fail with missing auth when <code>ollama-local</code> is saved as the key. Fixes #49736. Thanks @charles-zh.</li>
|
||||
<li>TUI/local models: treat visible gateway client labels such as <code>openclaw-tui</code> as the current requester session for session-aware tools, so Ollama tool calls no longer fail by resolving the UI label as a session id. Fixes #66391. Thanks @kickingzebra.</li>
|
||||
<li>Local models: route self-hosted OpenAI-compatible model discovery through the guarded fetch path pinned to the configured host, covering vLLM and SGLang setup without reopening local/LAN SSRF probes. Supersedes #46359. Thanks @cdxiaodong.</li>
|
||||
<li>Local models: classify terminated, reset, closed, timeout, and aborted model-call failures and attach a process memory snapshot to the diagnostic event, making LM Studio/Ollama RAM-pressure failures easier to prove from stability bundles. Refs #65551. Thanks @BigWiLLi111.</li>
|
||||
<li>Local models: pass configured provider request timeouts through OpenAI SDK transports and the model idle watchdog so long-running local or custom OpenAI-compatible streams use one timeout knob instead of hitting the SDK's 10-minute default or the 120s idle default. Fixes #63663. Thanks @aidiffuser.</li>
|
||||
<li>LM Studio: trust configured LM Studio loopback, LAN, and tailnet endpoints for guarded model requests by default, preserving explicit private-network opt-outs. Refs #60994. Thanks @tnowakow.</li>
|
||||
<li>Docker/setup: route Docker onboarding defaults for host-side LM Studio and Ollama through <code>host.docker.internal</code> and add the Linux host-gateway mapping to the bundled Compose file, so containerized gateways can reach local providers without using container loopback. Fixes #68684; supersedes #68702. Thanks @safrano9999 and @skolez.</li>
|
||||
<li>Agents/LM Studio: strip prior-turn Gemma 4 reasoning from OpenAI-compatible replay while preserving active tool-call continuation reasoning. Fixes #68704. Thanks @chip-snomo and @Kailigithub.</li>
|
||||
<li>LM Studio: allow interactive onboarding to leave the API key blank for unauthenticated local servers, using local synthetic auth while clearing stale LM Studio auth profiles. Fixes #66937. Thanks @olamedia.</li>
|
||||
<li>Plugins/startup/registry: reuse a Gateway <code>PluginLookUpTable</code> and one manifest registry pass across startup plugin IDs, plugin loading, deferred channel reloads, model pricing, read-only channel defaults, capability/provider/media resolution, manifest contracts, extractors, web fallback discovery, owner maps, and cold provider-discovery caches, with new startup-trace timing/count metrics for installed-index, manifest, startup-plan, and owner-map work. Thanks @shakkernerd and @mcaxtr.</li>
|
||||
<li>Mattermost: keep direct-message replies top-level by suppressing reply roots for DM delivery while preserving channel and group thread roots, and derive inbound chat kind from the trusted channel lookup instead of the websocket event channel type. Carries forward #60115, #55186, #72305, and #72659; refs #59758, #59981, #59791, and #57565. Thanks @vincentkoc, @jwchmodx, and @hnykda.</li>
|
||||
<li>Docker: pre-create <code>/home/node/.openclaw</code> with node ownership and private permissions so first-run Docker Compose named volumes no longer fail startup with EACCES. (#48072, #63959; fixes #61279) Thanks @timoxue and @jeanibarz.</li>
|
||||
<li>CLI/Gateway: treat local restart probe policy closes for connect, exact <code>device required</code>, pairing, and auth failures as Gateway reachability proof without accepting empty, broad standalone token/password/scope/role, or pair-substring 1008 close reasons. Fixes #48771; carries forward #48801; related #63491. Thanks @MarsDoge and @genoooool.</li>
|
||||
<li>Feishu: send outgoing interactive reply payloads as native cards with clickable buttons while preserving text, media, and document-comment fallbacks. Fixes #13175 and #58298; carries forward #47891. Thanks @Horacehxw.</li>
|
||||
<li>Process/Windows: decode command stdout and stderr from raw bytes with console-codepage awareness, while preserving valid UTF-8 output and multibyte characters split across chunks. Fixes #50519. Thanks @iready, @kevinten10, @zhangyongjie1997, @knightplat-blip, @heiqishi666, and @slepybear.</li>
|
||||
<li>Bonjour/Windows: hide the bundled mDNS advertiser's Windows ARP shell probe so Gateway startup no longer flashes command-prompt windows. Fixes #70238. Thanks @alexandre-leng, @PratikRai0101, @infinitypacific, and @tomerpeled.</li>
|
||||
<li>Agents/bootstrap: dedupe hook-injected bootstrap context files by workspace-relative path and store normalized resolved paths so duplicate relative and absolute hook paths no longer depend on the process cwd. (#59344; fixes #59319; related #56721, #56725, and #57587) Thanks @koen666.</li>
|
||||
<li>Agents/bootstrap: refresh cached workspace bootstrap snapshots on long-lived main-session turns when <code>AGENTS.md</code>, <code>SOUL.md</code>, <code>MEMORY.md</code>, or <code>TOOLS.md</code> change on disk, while preserving unchanged snapshot identity through the workspace file cache. (#64871; related #43901, #26497, #28594, #30896) Thanks @aimqwest and @mikejuyoon.</li>
|
||||
<li>macOS Gateway: detect installed-but-unloaded LaunchAgent split-brain states during status, doctor, and restart, and re-bootstrap launchd supervision before falling back to unmanaged listener restarts. Fixes #67335, #53475, and #71060; refs #58890, #60885, and #70801. Thanks @ze1tgeist88, @dafacto, and @vishutdhar.</li>
|
||||
<li>Plugins/install: treat mirrored core logger dependencies as staged bundled runtime deps so packaged Gateway starts do not crash when the external plugin-runtime-deps root is missing <code>tslog</code>. Fixes #72228; supersedes #72493. Thanks @deepujain.</li>
|
||||
<li>Build/plugins: preserve active bundled runtime-dependency staging temp directories owned by live build processes so overlapping postbuild runs no longer delete each other's staged deps mid-prune. Supersedes #72220. Thanks @VACInc.</li>
|
||||
<li>Plugins/install: hide bundled runtime-dependency npm child windows on Windows across Gateway startup, postinstall, and packaged staging paths so Telegram/Anthropic dependency repair no longer flashes shell windows. Fixes #72315. Thanks @athuljayaram and @joshfeng.</li>
|
||||
<li>Agents/Windows: normalize lazy agent runtime imports before Node ESM loading so Windows drive-letter <code>subagent-registry</code> runtime paths no longer fail every agent task with <code>ERR_UNSUPPORTED_ESM_URL_SCHEME</code>. Fixes #72636; carries forward #72716. Thanks @Andyz-CData and @xialonglee.</li>
|
||||
<li>Plugins/Windows: normalize lazy plugin service override imports before Node ESM loading so drive-letter browser-control module paths no longer fail with <code>ERR_UNSUPPORTED_ESM_URL_SCHEME</code>. Fixes #72573; supersedes #72599 and #72582. Thanks @llzzww316, @feineryonah-byte, and @WuKongAI-CMU.</li>
|
||||
<li>Browser/plugins: load <code>playwright-core</code> through the browser runtime shim so packaged installs can run Playwright actions from staged plugin runtime deps after doctor/startup repair. Fixes #72168; supersedes #72238. Thanks @zdg1110 and @yetval.</li>
|
||||
<li>Plugins/install: stage bundled plugin runtime dependencies before Gateway startup, drain update restarts, and materialize plugin-owned root chunks in external mirrors so staged deps resolve under native ESM. Fixes #72058; supersedes #72084. Thanks @amnesia106 and @drvoss.</li>
|
||||
<li>TTS/SecretRef: resolve <code>messages.tts.providers.*.apiKey</code> from the active runtime snapshot so SecretRef-backed MiniMax and other TTS provider keys work in runtime reply/audio paths. Fixes #68690. Thanks @joshavant.</li>
|
||||
<li>Gateway/install: surface systemd user-bus recovery hints during Linux service activation and retry via the target user scope when <code>systemctl --user</code> reports no-medium bus failures, without letting stale <code>SUDO_USER</code> override <code>sudo -u</code> installs. Fixes #39673; refs #44417 and #63561. Thanks @Arbor4, @myrsu, @mssteuer, and @boyuaner.</li>
|
||||
<li>CLI/nodes: make unfiltered <code>openclaw nodes list</code> prefer the effective paired-node view used by <code>nodes status</code> while preserving pending rows, pairing-scope fallback, terminal-safe table rendering, and paired JSON metadata. Fixes #46871; carries forward #65772 through the ProjectClownfish #72619 repair. Thanks @skainguyen1412.</li>
|
||||
<li>CLI/startup: read generated startup metadata from the bundled <code>dist</code> layout before falling back to live help rendering, so root/browser help and channel-option bootstrap stay on the fast path. Thanks @vincentkoc.</li>
|
||||
<li>Feishu/Lark: stop treating broadcast-only <code>@all</code>/<code>@_all</code> messages as bot mentions while preserving direct bot mentions, including messages that also include <code>@all</code>. Fixes #37706. Thanks @JosepLee.</li>
|
||||
<li>CLI/help: treat positional <code>help</code> invocations like <code>openclaw channels help</code> as help paths for startup gating, avoiding model/auth warmup while preserving positional arguments such as <code>openclaw docs help</code>. Thanks @gumadeiras.</li>
|
||||
<li>Web search: route plugin-scoped web_search SecretRefs through the active runtime config snapshot so provider execution receives resolved credentials across app/runtime paths, including <code>plugins.entries.brave.config.webSearch.apiKey</code>. Fixes #68690. Thanks @VACInc.</li>
|
||||
<li>Voice Call: allow SecretRef-backed Twilio auth tokens and call-specific OpenAI/ElevenLabs TTS API keys through the plugin config surface. Fixes #68690. Thanks @joshavant.</li>
|
||||
<li>Google Meet/Voice Call: clean stale chrome-node realtime bridges before rejoining, expose bridge inspection, tolerate transient node input pull failures, default Chrome command-pair audio to 24 kHz PCM16 while preserving legacy 8 kHz G.711 mu-law pairs, handle Gemini Live interruptions/VAD and function-response names correctly, route stateful <code>google_meet</code> tools through the gateway runtime, support <code>realtime.agentId</code>, and send non-blocking consult continuations before long tool-backed answers finish. Fixes #72371, #72525, #72523, #72440, and #72425; (#72372, #72524, #72381, #72441, #72189, #72426) Thanks @BsnizND and @VACInc.</li>
|
||||
<li>Discord/media: keep incidental Markdown image badges in final replies as text unless a channel opts into Markdown-image media extraction, while preserving Telegram Markdown-image media replies and explicit <code>MEDIA:</code> attachments. Fixes #72642. Thanks @solavrc and @Bartok9.</li>
|
||||
<li>Matrix/E2EE: stabilize recovery and broken-device QA flows while avoiding Matrix device-cleanup sync races that could leave shutdown-time crypto work running. Thanks @gumadeiras.</li>
|
||||
<li>Cron: apply <code>cron.maxConcurrentRuns</code> to the nested isolated-agent lane, start isolated execution timeouts only after the runner enters that lane, keep legacy flat <code>jobs.json</code> rows loadable, invalidate stale pending runtime slots after schedule edits, and preserve due slots for formatting-only rewrites. Fixes #72707, #27996, #71607, and #41783; carries forward #71651. Thanks @kagura-agent, @xialonglee, @fagnersouza666, @ayanesakura, and @Hurray0.</li>
|
||||
<li>Cron/delivery: classify isolated successes, quiet <code>NO_REPLY</code> turns, model/provider failures, execution denials, <code>--no-deliver</code> traces, skipped-job alerts, and verified delivery outcomes correctly so cron history, retries, and failure counters reflect what actually happened. Fixes #72732, #50170, #43604, #68452, #60846, #72210, and #67172; follow-up to #54188; carries forward #43631, #68453, #72219, and #67186. Thanks @zNatix, @pixeldyn, @ChickenEggRoll, @SPFAdvisors, @anyech, @slideshow-dingo, @hatemclawbot-collab, @xydigit-sj, @oc-gh-dr, @hclsys, and @1yihui.</li>
|
||||
<li>Cron/routing: preserve direct Telegram thread/account IDs, explicit Discord <code>user:</code>/<code>channel:</code> delivery targets, and <code>session:<id></code> failure-destination routing so reminders, cron announcements, and failure alerts keep the intended recipient kind across direct and group chats. Fixes #44270; refs #62777; carries forward #44325, #44351, #44412, #72657, #68535, and #62798. Thanks @RunMintOn, @arkyu2077, @0xsline, @vincentkoc, @slideshow-dingo, @likewen-tech, and @neeravmakwana.</li>
|
||||
<li>Subagents: keep the delegated task only in the subagent system prompt and send a short initial kickoff message, avoiding duplicate task tokens while preserving multiline task formatting. Fixes #72019; carries forward #72053. Thanks @Wizongod and @ly85206559.</li>
|
||||
<li>Onboarding/GitHub Copilot: add manifest-owned <code>--github-copilot-token</code> support for non-interactive setup, including env fallback, tokenRef storage in ref mode, saved-profile reuse, and current Copilot default-model wiring. Refs #50002 and supersedes #50003. Thanks @scottgl9.</li>
|
||||
<li>Gateway/install: add a validated <code>--wrapper</code>/<code>OPENCLAW_WRAPPER</code> service install path that persists executable LaunchAgent/systemd wrappers across forced reinstalls, updates, and doctor repairs instead of falling back to raw node/bun <code>ProgramArguments</code>. Fixes #69400. (#72445) Thanks @willtmc.</li>
|
||||
<li>Plugins: fail plugin registration when loader-owned acceptance gates reject missing hook names or memory-only capability registration from non-memory plugins, surfacing the issue through plugin status and doctor instead of silently dropping the registration. Fixes #72459. Thanks @amknight.</li>
|
||||
<li>macOS Gateway: write launchd services with a state-dir <code>WorkingDirectory</code>, use a durable state-dir temp path instead of freezing macOS session <code>TMPDIR</code>, create that temp directory before bootstrap, and label abort-shaped launchd exits as <code>SIGABRT/abort</code> in status output. Fixes #53679 and #70223; refs #71848. Thanks @dlturock, @stammi922, and @palladius.</li>
|
||||
<li>Control UI/update: make <code>Update now</code> require a real gateway process replacement, report skipped/error update outcomes with stable reasons, and verify the running gateway version after restart so global installs cannot silently keep old code in memory. Fixes #62492; addresses #64892 and #63562. Thanks @IAMSamuelRodda.</li>
|
||||
<li>Exec approvals: accept runtime-owned <code>source: "allow-always"</code> and <code>commandText</code> allowlist metadata in gateway and node approval-set payloads so Control UI round-trips no longer fail with <code>unexpected property 'source'</code>. Fixes #60000; carries forward #60064. Thanks @sd1471123, @sharkqwy, and @luoyanglang.</li>
|
||||
<li>Exec/node: skip approval-plan preparation for full-trust <code>host=node</code> runs so interpreter and script commands no longer fail with <code>SYSTEM_RUN_DENIED: approval cannot safely bind</code> when effective policy is <code>security=full</code> and <code>ask=off</code>. Fixes #48457 and duplicate #69251. Thanks @ajtran303, @jaserNo1, @Blakeshannon, @lesliefag, and @AvIsBeastMC.</li>
|
||||
<li>Exec/node: synthesize a local approval plan when a paired node advertises <code>system.run</code> without <code>system.run.prepare</code>, unblocking approval-required <code>host=node</code> exec on current macOS companion nodes while preserving remote prepare for node hosts that support it. Fixes #37591 and duplicate #66839; carries forward #69725. Thanks @soloclz.</li>
|
||||
<li>Memory/QMD: prefer QMD's <code>--mask</code> collection pattern flag so root memory indexing stays scoped to <code>MEMORY.md</code> instead of widening to every markdown file in the workspace. Fixes #65480; supersedes #65481 and #66259. Thanks @ccage-simp, @Bortlesboat, @seank-com, and @crazyscience.</li>
|
||||
<li>Memory/doctor: treat the specific <code>gateway timeout after ...</code> gateway memory probe result as inconclusive instead of reporting embeddings not ready, while preserving warnings for explicit failures. Fixes #44426; carries forward #46576 with the Greptile review feedback applied. Thanks Cengiz (@ghost).</li>
|
||||
<li>Gateway/startup: defer QMD, core request handlers, setup wizard, CLI outbound senders, plugin HTTP routes, chat/session projection, node session runtime validation, embedded-run activity reads, MCP loopback server imports, channel runtime helpers, HTTP/canvas/plugin auth helpers, isolated cron imports, and hook dispatch parsing until their request or shutdown paths, while making plain <code>gateway status</code> use a parse-only config snapshot so no-plugin boots and status reads avoid broad runtime fanout. Thanks @vincentkoc.</li>
|
||||
<li>Lobster/Gateway: memoize repeated Ajv schema compilation before loading the embedded Lobster runtime so scheduled workflows and <code>llm.invoke</code> loops stop growing gateway heap on content-identical schemas. Fixes #71148. Thanks @cmi525, @vsolaz, and @vincentkoc.</li>
|
||||
<li>Codex harness: normalize cached input tokens before session/context accounting so prompt cache reads are not double-counted in <code>/status</code>, <code>session_status</code>, or persisted <code>sessionEntry.totalTokens</code>. Fixes #69298. Thanks @richardmqq.</li>
|
||||
<li>Hooks/session-memory: use the host local timezone for memory filenames, fallback timestamp slugs, and markdown headers instead of UTC dates. Fixes #46703. (#46721) Thanks @Astro-Han.</li>
|
||||
<li>Gateway health: preserve live runtime-backed channel/account state in <code>gateway.health</code> snapshots and cached refreshes while keeping raw probe payloads on sensitive/admin paths only. (#39921, #42586, #46527, #52770, #42543) Thanks @FAL1989, @rstar327, @0xble, and @ajayr.</li>
|
||||
<li>Feishu: extract quoted/replied interactive-card text across schema 1.0, schema 2.0, i18n, template-variable, and post-format fallback shapes without carrying broad generated/config churn from related parser experiments. (#38776, #60383, #42218, #45936) Thanks @lishuaigit, @lskun, @just2gooo, and @Br1an67.</li>
|
||||
<li>Telegram/agents: hide raw failed write/edit warning messages in Telegram when the assistant already explicitly acknowledges the failed action, while keeping warnings when the reply claims success or omits the failure; #39406 remains the broader configurable delivery-policy follow-up. Fixes #51065; covers #39631. Thanks @Bartok9 and @Bortlesboat.</li>
|
||||
<li>Exec approvals: accept a symlinked <code>OPENCLAW_HOME</code> as the trusted approvals root while still rejecting symlinked <code>.openclaw</code> path components below it. (#64663) Thanks @FunJim.</li>
|
||||
<li>Logging: add top-level <code>hostname</code>, flattened <code>message</code>, and available <code>agent_id</code>, <code>session_id</code>, and <code>channel</code> fields to file-log JSONL records for multi-agent filtering without removing existing structured log arguments. Fixes #51075. Thanks @stevengonsalvez.</li>
|
||||
<li>ACP: route server logs to stderr before Gateway config/bootstrap work so ACP stdout remains JSON-RPC only for IDE integrations. Fixes #49060. Thanks @Hollychou924.</li>
|
||||
<li>Logging: propagate internal request trace scopes through Gateway HTTP requests and WebSocket frames so file logs, diagnostic events, agent run traces, model-call traces, OTEL spans, and trusted provider <code>traceparent</code> headers share a correlatable <code>traceId</code> without logging raw request or model content. Fixes #40353. Thanks @liangruochong44-ui.</li>
|
||||
<li>Diagnostics/OTEL: capture privacy-safe model-call request payload bytes, streamed response bytes, first-response latency, and total duration in diagnostic events, plugin hooks, stability snapshots, and OTEL model-call spans/metrics without logging raw model content. Fixes #33832. Thanks @wwh830.</li>
|
||||
<li>Logging: write validated diagnostic trace context as top-level <code>traceId</code>, <code>spanId</code>, <code>parentSpanId</code>, and <code>traceFlags</code> fields in file-log JSONL records so traced requests and model calls are easier to correlate in log processors. Refs #40353. Thanks @liangruochong44-ui.</li>
|
||||
<li>Logging/sessions: apply configured redaction patterns to persisted session transcript text and accept escaped character classes in safe custom redaction regexes, so transcript JSONL no longer keeps matching sensitive text in the clear. Fixes #42982. Thanks @panpan0000.</li>
|
||||
<li>Agents/sessions: let <code>sessions_spawn runtime="subagent"</code> ignore ACP-only <code>streamTo</code> and <code>resumeSessionId</code> fields while keeping ACP passthrough and documenting <code>streamTo</code> as ACP-only. Fixes #43556 and #63120; covers #56326, #61724, #64714, and #67248; carries forward #68397, #65282, #58686, #56342, and #40102. Thanks @skernelx, @damselem, @Br1an67, @Mintalix, @IsaacAPerez, @vvitovec, @Sanjays2402, @shenkq97, and @1034378361.</li>
|
||||
<li>Providers/Ollama: honor <code>/api/show</code> capabilities, custom Modelfile <code>PARAMETER num_ctx</code>, configured provider/model context defaults, whitelisted native params such as <code>temperature</code>, <code>top_p</code>, and <code>think</code>, and native thinking effort levels so local models get accurate tools, context, and thinking behavior without forcing full-context VRAM use. Fixes #64710, duplicate #65343, #68344, #44550, #52206, #49684, #68662, #48010, #71584, and #44786; supersedes #69464; carries forward #44955. Thanks @yuan-b, @netherby, @xilopaint, @Diyforfun2026, @neeravmakwana, @taitruong, @armi0024, @LokiCode404, @zhouZcong, @dshenster-byte, @tangzhi, @pandego, @maweibin, @Adam-Researchh, @EmpireCreator, @g0st1n, and @voltwake.</li>
|
||||
<li>Image tool/media: honor <code>tools.media.image.timeoutSeconds</code> and matching per-model image timeouts in explicit image analysis, including the MiniMax VLM fallback path, so slow local vision models are not capped by hardcoded 30s/60s aborts. Fixes #67889; supersedes #67929. Thanks @AllenT22 and @alchip.</li>
|
||||
<li>Providers/Ollama: strip custom provider prefixes before native chat/embedding requests, skip ambient localhost discovery unless config/auth opts in, handle custom remote <code>api: "ollama"</code> providers, accept OpenAI SDK-style <code>baseURL</code>, scope synthetic local auth and embedding bearer headers to declared host boundaries, resolve custom-named local providers for subagents, add provider-scoped model request timeouts, preserve explicit input modalities, and document <code>params.keep_alive</code> plus local/LAN/cloud/multi-host/web-search/embedding/thinking setup recipes. Fixes #72353, #56939, #62533, #43945, #64541, #68796, and #39690; supersedes #57116, #62549, #69261, #69857, #65143, and #66511; refs #43945; carries forward #43224 and #39785. Thanks @maximus-dss, @hclsys, @IanxDev, @tsukhani, @issacthekaylon, @Julien-BKK, @Linux2010, @hyspacex, @maxramsay, @Meli73, @LittleJakub, @Juankcba, @uninhibite-scholar, @yfge, @Skrblik, and @Mriris.</li>
|
||||
<li>Providers/Ollama: move memory embeddings to <code>/api/embed</code> with batched <code>input</code>, route local web search through Ollama's signed daemon proxy while keeping cloud auth scoped, treat Ollama memory embeddings as key-optional in doctor, and keep model usage visible by estimating native transcript usage when <code>/api/chat</code> omits counters. Fixes #39983, #69132, and #46584; carries forward #39112. Thanks @sskkcc, @LiudengZhang, @yoon1012, @hyspacex, @fengly78, and @TylonHH.</li>
|
||||
<li>Agents/Ollama: parse stringified native tool-call arguments, retry native empty/thinking-only turns, accept already-prefixed LLM task model overrides, apply provider-owned replay normalization for Cloud models, validate explicit <code>--thinking max</code>, show resolved thinking defaults in Control UI, and include configured provider models in <code>models list --provider</code>. Fixes #69735, #50052, #71697, #71584, #72407, and #65207; supersedes #69910; carries forward #66552 and #61223. Thanks @rongshuzhao, @yfge, @L3G, @ralphy-maplebots, @Hollychou924, @ismael-81, @g0st1n, @NotecAG, and @drzeast-png.</li>
|
||||
<li>Providers/PDF/Ollama: add bounded network timeouts for Ollama model pulls and native Anthropic/Gemini PDF analysis requests so unresponsive provider endpoints no longer hang sessions indefinitely. Fixes #54142; supersedes #54144 and #54145. Thanks @jinduwang1001-max and @arkyu2077.</li>
|
||||
<li>Docker/QA: add observability coverage to the normal Docker aggregate so QA-lab OTEL and Prometheus diagnostics run inside Docker. Thanks @vincentkoc.</li>
|
||||
<li>Auto-reply: poison inbound message dedupe after replay-unsafe provider/runtime failures so retries stay safe before visible progress but cannot duplicate messages after block output, tool side effects, or session progress. Fixes #69303; keeps #58549 and #64606 as duplicate validation. Thanks @martingarramon, @NikolaFC, and @zeroth-blip.</li>
|
||||
<li>Agents/model fallback: keep auto-persisted fallback model overrides selected across turns until <code>/new</code> or reset clears them, avoiding repeated probes of a known-bad primary while <code>/status</code> shows the selected and active models. Thanks @kibedu.</li>
|
||||
<li>Agents/model fallback: jump directly to a known later live-session model redirect instead of walking unrelated fallback candidates, while preserving the already-landed live-session/fallback loop guard. Fixes #57471; related loop family already closed via #58496. Thanks @yuxiaoyang2007-prog.</li>
|
||||
<li>Gateway/Bonjour: keep @homebridge/ciao cancellation handlers registered across advertiser restarts so late probing cancellations cannot crash Linux and other mDNS-churned gateways.</li>
|
||||
<li>Plugins/startup: load the default <code>memory-core</code> slot during Gateway startup when permitted so active-memory recall can call <code>memory_search</code> and <code>memory_get</code> without requiring an explicit <code>plugins.slots.memory</code> entry, while preserving <code>plugins.slots.memory: "none"</code>.</li>
|
||||
<li>Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet.</li>
|
||||
<li>Plugins/compat/CLI: inventory doctor-side deprecation migrations separately from runtime plugin compatibility, add dated records for legacy extension-api, memory registration, provider hook/type aliases, runtime aliases, channel SDK helpers, and approval/test utility shims, refresh the persisted registry after managed plugin removals, make plugin install/uninstall writes conflict-aware, clear stale denylists, and fail tracked plugin/hook updates or unloadable package installs instead of leaving stale state. Thanks @vincentkoc.</li>
|
||||
<li>WebChat/Control UI: support non-video file attachments in chat uploads while preserving the existing image attachment path and MIME-sniff fallback for generic image uploads. (#70947) Thanks @IAMSamuelRodda.</li>
|
||||
<li>Skills/memory: restore Chokidar v5 hot reloads by watching concrete skill and memory roots with filters, including SKILL.md removals and deleted skill folders without broad workspace recursion. Fixes #27404, #33585, and #41606. Thanks @shelvenzhou, @08820048, and @rocke2020.</li>
|
||||
<li>Gateway/chat: keep duplicate attachment-backed <code>chat.send</code> retries with the same idempotency key on the documented in-flight path so aborts still target the real active run. Fixes #70139. Thanks @Feelw00.</li>
|
||||
<li>Gateway/session rows: report the same config-resolved thinking default that runtime sessions use, including global and per-agent defaults, so Control UI and TUI default labels stay aligned. (#71779, #70981, #71033, #70302) Thanks @chen-zhang-cs-code, @SymbolStar, and @cholaolu-boop.</li>
|
||||
<li>Plugins: share package entrypoint resolution between install and discovery, reject mismatched <code>runtimeExtensions</code>, and cache bundled runtime-dependency manifest reads during scans.</li>
|
||||
<li>WhatsApp/Web: keep quiet but healthy linked-device sessions connected by basing the watchdog on WhatsApp Web transport activity, while retaining a longer app-silence cap so frame activity cannot mask a stuck session forever. Fixes #70678; carries forward the focused #71466 approach and keeps #63939 as related configurable-timeout follow-up. Thanks @vincentkoc and @oromeis.</li>
|
||||
<li>Discord/gateway: count failed health-monitor restart attempts toward cooldown and hourly caps, and evict stale account lifecycle state during channel reloads so repeated Discord gateway recovery cannot loop on old status. Fixes #38596. (#40413) Thanks @jellyAI-dev and @vashquez.</li>
|
||||
<li>Cron/context engine: run isolated cron jobs under run-scoped context-engine session keys so prior runs of the same job are not inherited unless the job is explicitly session-bound. (#72292) Thanks @jalehman.</li>
|
||||
<li>Control UI: localize command palette labels, categories, skill shortcuts, footer hints, and connect-command copy labels while preserving localized command palette search matching. (#61130, #61119) Thanks @rubensfox20.</li>
|
||||
<li>Plugins/memory-lancedb: request float embedding responses from OpenAI-compatible servers so local providers that default SDK requests to base64 no longer return dimension-mismatched LanceDB vectors while preserving configured dimensions. Fixes #45982. (#59048, #46069, #45986) Thanks @deep-introspection, @xiaokhkh, @caicongyang, and @thiswind.</li>
|
||||
<li>Plugins/memory-lancedb: advance auto-capture cursors per session only after messages are processed or intentionally skipped, retry failed messages, survive compacted histories, and clear cursor state on session end. Fixes #71349; carries forward #42083. Thanks @as775116191.</li>
|
||||
<li>Plugins/memory-core: respect configured memory-search embedding concurrency during non-batch indexing so local Ollama embedding backends can serialize indexing instead of flooding the server. Fixes #66822. (#66931) Thanks @oliviareid-svg and @LyraInTheFlesh.</li>
|
||||
<li>Docker/update smoke: keep the package-derived update-channel fixture on package-shipped files and make its UI build stub create the asset the updater verifies. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/models: repair legacy <code>models.providers.*.api = "openai"</code> config values to <code>openai-completions</code>, and skip providers with future stale API enum values during startup instead of bricking the gateway. Fixes #72477. (#72542) Thanks @JooyoungChoi14 and @obviyus.</li>
|
||||
<li>Gateway/skills: redact <code>apiKey</code> and secret-named <code>env</code> values from the <code>skills.update</code> RPC response to prevent leaking credentials into WebSocket traffic, client logs, or session transcripts. Config is still written to disk in full; only the response payload is redacted. (#69998) Thanks @Ziy1-Tan.</li>
|
||||
<li>Plugins/CLI: let flag-driven <code>openclaw channels add</code> install the selected channel plugin from its default source without opening an interactive prompt, fixing published npm Telegram setup in stdin-closed automation.</li>
|
||||
<li>Onboarding/setup: keep first-run config reads, plugin compatibility notices, OpenAI Codex auth, post-auth default-model policy lookup, skip-auth, provider-scoped model pickers, and post-model sanity checks on cold manifest/setup metadata unless the user chooses to browse all models, avoiding full plugin/provider runtime loads between prompts. Thanks @shakkernerd.</li>
|
||||
<li>Gateway/Bonjour: suppress known @homebridge/ciao cancellation and network assertion failures through scoped process handlers so malformed mDNS packets or restricted VPS networking disable/restart Bonjour instead of crashing the gateway. Fixes #67578. Thanks @zenassist26-create.</li>
|
||||
<li>Discord: keep late clicks on already-resolved exec approval buttons quiet when elevated mode auto-resolved the request, while still surfacing real approval submission failures. Fixes #66906. Thanks @rlerikse.</li>
|
||||
<li>Telegram: send a fresh final message for long-lived preview-streamed replies so the visible Telegram timestamp reflects completion time instead of the preview creation time. Thanks @rubencu.</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.4.26/OpenClaw-2026.4.26.zip" length="48222029" type="application/octet-stream" sparkle:edSignature="6wgFZUyyU09Y6nvD9T1Ufq7Plo0Wzfg+L9r80DCaNMMuwebcKWAsMVSP3RvhRhTxVMax8toUDYg3gb/vOiE5BA=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.4.25</title>
|
||||
<pubDate>Mon, 27 Apr 2026 13:34:25 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026042590</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.4.25</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.4.25</h2>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>Voice replies get a full TTS upgrade: <code>/tts latest</code>, chat-scoped auto-TTS controls, personas, per-agent/per-account overrides, and new Azure Speech, Xiaomi, Local CLI, Inworld, Volcengine, and ElevenLabs v3 provider coverage. Thanks @leonchui, @zoujiejun, @solar2ain, @cshape, @xuruiray, @itsuzef, and @barronlroth.</li>
|
||||
<li>Plugin startup and install paths move to the cold persisted registry, cutting broad manifest scans while making plugin update, repair, provider discovery, and install metadata more deterministic. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>OpenTelemetry coverage expands across model calls, token usage, tool loops, harness runs, exec processes, outbound delivery, context assembly, and memory pressure with bounded low-cardinality attributes. Thanks @vincentkoc, @jlapenna, @Lidang-Jiang, and @oc-factus.</li>
|
||||
<li>Browser automation gets safer tab URLs, iframe-aware role snapshots, CDP readiness tuning, headless one-shot launch, and deeper browser doctor probes for slow hosts. Thanks @beat843796 and @BenediktSchackenberg.</li>
|
||||
<li>Control UI and setup flows add PWA/Web Push support, Crestodian first-run repair, TUI setup, context mode selection, and a shorter startup greeting. Thanks @eduardocruz, @SebTardif, and @kevinlin-openai.</li>
|
||||
<li>Install/update hardening covers Windows, macOS, Linux, Docker, bundled plugin runtime deps, Node service restarts, LaunchAgent token rotation, and mixed-version gateway verification. Thanks @Kobevictor, @igormf, @abhinas90, @jsompis, @Solvely-Colin, and @gucasbrg.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>TTS/WhatsApp: add <code>/tts latest</code> read-aloud support with duplicate suppression and <code>/tts chat on|off|default</code> session-scoped auto-TTS overrides, completing the on-demand voice-note UX for current-chat replies. Fixes #66032.</li>
|
||||
<li>TTS/channels: resolve channel and account TTS overrides generically, enabling Feishu and QQBot accounts to deep-merge <code>channels.<channel>.accounts.<id>.tts</code> over global and per-agent TTS config. Thanks @sahilsatralkar.</li>
|
||||
<li>TTS/agents: allow <code>agents.list[].tts</code> to override global <code>messages.tts</code> for per-agent voices, and make <code>/tts audio</code>, <code>/tts status</code>, and the <code>tts</code> agent tool honor the active voice/provider override while keeping shared provider credentials and preferences in the existing TTS config surface.</li>
|
||||
<li>Providers/Azure Speech: add Azure Speech as a bundled TTS provider with Speech-resource auth, voice listing, SSML escaping, native Ogg/Opus voice-note output, and telephony output. (#51776) Thanks @leonchui.</li>
|
||||
<li>Google Meet: add calendar-backed attendance export workflows, export manifests, dry-run previews, and tool parity for meeting records.</li>
|
||||
<li>Control UI: add PWA install support and Web Push notifications for Gateway chat. (#44590) Thanks @eduardocruz.</li>
|
||||
<li>Browser automation: add safe tab URLs in agent responses plus a CDP-native role snapshot fallback with iframe-aware refs, cursor-clickable detection, target attach preparation, and <code>openclaw browser doctor --deep</code> live snapshot probing.</li>
|
||||
<li>CLI/image generation: expose generic <code>--background</code> on <code>openclaw infer image generate</code> and <code>openclaw infer image edit</code>, keep <code>--openai-background</code> as an OpenAI alias, and let fal image generation honor <code>--output-format png|jpeg</code>.</li>
|
||||
<li>Browser/config: allow local managed Chrome launch discovery and post-launch CDP readiness timeouts to be raised for slower hosts such as Raspberry Pi. Fixes #66803. Thanks @beat843796.</li>
|
||||
<li>Discord: allow <code>channels.discord.voice.model</code> to override the LLM used for voice channel responses while keeping STT and TTS on their existing media settings. (#64368) Thanks @mrdavey.</li>
|
||||
<li>Browser/CLI: add <code>openclaw browser start --headless</code> as a one-shot local managed browser launch override without rewriting persisted browser config. Thanks @BenediktSchackenberg.</li>
|
||||
<li>CLI/Crestodian/TUI: add the first-run setup helper, local planner fallback, full-TUI interactive Crestodian, startup progress indicators, context mode selector, and a shorter startup greeting. (#71720, #71760) Thanks @SebTardif and @kevinlin-openai.</li>
|
||||
<li>Plugins: migrate the local plugin registry automatically during package install/update, keeping install metadata in the plugin index while indexing existing plugin manifests for the new cold registry path. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Plugins/doctor: make <code>openclaw doctor --fix</code> refresh the plugin index and cold registry index when needed without treating plugin install records as authored config. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Plugins/hooks: add before-agent-finalize hooks, cron <code>jobId</code> hook context, bounded native permission fingerprints, and Codex MCP hook relay support. (#71765, #71758, #71707) Thanks @vincentkoc and @pashpashpash.</li>
|
||||
<li>Plugins/tokenjuice: bump the bundled tokenjuice runtime to 0.6.3. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: align model-call GenAI span attributes with OpenTelemetry stability opt-in semantics, keeping legacy <code>gen_ai.system</code> by default while emitting <code>gen_ai.provider.name</code> under <code>OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental</code>. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: support signal-specific OTLP endpoint overrides for traces, metrics, and logs via config or standard OTEL environment variables. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: emit bounded telemetry exporter health diagnostics for startup and log-export failures without exporting raw error text. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: export agent harness lifecycle telemetry as bounded <code>openclaw.harness.run</code> spans and <code>openclaw.harness.duration_ms</code> metrics so QA-lab, Codex, and future harnesses share one trace shape. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/trace: propagate W3C <code>traceparent</code> headers from trusted model-call trace context to provider transports while replacing caller-supplied traceparent values. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/Prometheus: add a bundled <code>diagnostics-prometheus</code> plugin with a protected gateway scrape route for low-cardinality diagnostics metrics. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/CLI: add <code>openclaw plugins registry</code> for explicit persisted-registry inspection and <code>--refresh</code> repair without making normal startup rescan plugin locations. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/CLI: make <code>openclaw plugins list</code> read the cold persisted registry snapshot by default, leaving module-aware diagnostics to <code>plugins doctor</code> and <code>plugins inspect</code>. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/startup: move gateway startup plugin planning onto the versioned cold registry index, with postinstall repair for older registry files that predate startup metadata. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/startup: normalize startup and provider plugin enablement through registry aliases so boot paths do not need the legacy manifest alias scan. Thanks @vincentkoc.</li>
|
||||
<li>Providers/plugins: resolve provider ownership, provider discovery scopes, and catalog-hook provider ids from the cold plugin registry instead of rescanning manifests on those paths. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/registry: keep installed plugin index records focused on install/state/load paths and resolve plugin capabilities from manifests scoped to indexed plugins. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/registry: route cold manifest and capability lookups through the installed plugin index so setup, channels, config, secrets, doctor, and provider metadata paths avoid broad plugin-root scans before runtime execution. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: speed up <code>models list --all --provider <id></code> for static manifest-backed providers by loading catalog rows through the installed plugin index instead of broad manifest scans or runtime suppression hooks. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: use OpenClaw Provider Index preview rows as the final cold fallback for installable providers, while keeping user config, installed manifests, and refreshed cache rows above provider-index metadata. Thanks @vincentkoc.</li>
|
||||
<li>Providers/plugins: keep onboarding and auth-choice setup lists on cold manifest/install metadata and add Provider Index install metadata for not-yet-installed provider plugins. Thanks @vincentkoc.</li>
|
||||
<li>Providers/plugins: keep provider setup guidance and configure auth imports on cold manifest metadata, with a regression guard against static provider-runtime imports on setup/configure list paths. Thanks @vincentkoc.</li>
|
||||
<li>CLI/capabilities: keep capability command registration from importing the models auth runtime until <code>model auth login</code> actually runs. Thanks @vincentkoc.</li>
|
||||
<li>CLI/configure: keep web-search configure prompts on cold plugin registry metadata until the user chooses managed search setup. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/chat commands: refresh the persisted plugin registry after <code>/plugins enable</code> and <code>/plugins disable</code>, matching the CLI mutation path. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/compat: mark <code>OPENCLAW_DISABLE_PERSISTED_PLUGIN_REGISTRY</code> as a deprecated break-glass switch and point operators at registry repair instead. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/compat: expand the central compatibility registry with dated owners, replacements, and maximum three-month removal targets for legacy SDK, manifest, setup, registry-migration, and agent-runtime surfaces. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/registry: ignore stale persisted registry reads when plugin policy no longer matches current config, and stamp generated registry files with a do-not-edit warning. Thanks @vincentkoc.</li>
|
||||
<li>Config/plugins: keep plugin command-alias validation on cold manifest metadata instead of importing the runtime alias resolver. Thanks @vincentkoc.</li>
|
||||
<li>Security/plugins: keep web-search credential presence checks on cold config, env, and manifest metadata instead of importing web-search provider runtime. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: surface provider request identifiers as bounded hashes on model-call diagnostics and span events, without exporting raw request IDs or metric labels. Thanks @Lidang-Jiang and @vincentkoc.</li>
|
||||
<li>Plugins/diagnostics: add metadata-only <code>model_call_started</code> and <code>model_call_ended</code> hooks for provider/model call telemetry without exposing prompts, responses, headers, request bodies, or raw provider request IDs. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: emit bounded context assembly diagnostics and export <code>openclaw.context.assembled</code> spans with prompt/history sizes but no prompt, history, response, or session-key content. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: export existing tool-loop diagnostics as <code>openclaw.tool.loop</code> counters and spans without loop messages, session identifiers, params, or tool output. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: export diagnostic memory samples and pressure as bounded memory histograms, counters, and pressure spans to help spot leak regressions without session or payload data. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add the GenAI <code>gen_ai.client.token.usage</code> histogram for input/output model usage while keeping session identifiers and aggregate cache counters out of the semantic metric. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add a bounded <code>openclaw.agent</code> label to OpenClaw token metrics so per-agent Grafana dashboards can group usage without exporting session identifiers. Thanks @oc-factus.</li>
|
||||
<li>Plugins/install: consolidate managed plugin install metadata into the state-managed plugin index at <code>plugins/installs.json</code>, replacing the temporary <code>plugins/installed-index.json</code> path and removing <code>plugins.installs</code> as an authored config surface. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Diagnostics/OTEL: add the GenAI <code>gen_ai.client.operation.duration</code> histogram for model-call latency in seconds with bounded provider/model/API and error attributes. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add GenAI usage token attributes to model-usage spans, including cache read/write input token counts without session identifiers or prompt/response content. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: include bounded GenAI operation, provider, and request-model attributes on model-usage spans so token usage remains self-describing without diagnostic identifiers. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: keep model-usage span GenAI provider attributes aligned with the existing semantic-convention opt-in policy, using legacy <code>gen_ai.system</code> unless latest experimental GenAI conventions are enabled. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: keep <code>gen_ai.request.model</code> present on GenAI token usage metrics with a bounded <code>unknown</code> fallback when model usage events do not include a model. Thanks @vincentkoc.</li>
|
||||
<li>Docs/OTEL: document the GenAI token and model-call duration metrics, model-usage span attributes, and <code>OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental</code> provider-attribute behavior. Thanks @vincentkoc.</li>
|
||||
<li>Docs: refresh the MCP, model provider, doctor, troubleshooting, BlueBubbles, media generation, TTS, subagents, skills, cron/tasks, exec approvals, and voice-call guides with structured Steps, Tabs, and Accordion content.</li>
|
||||
<li>Diagnostics/trace: add an internal traceparent propagation helper that only formats trusted dispatcher metadata, keeping plugin-emitted diagnostic traces out of outbound propagation by default. Thanks @vincentkoc.</li>
|
||||
<li>Diagnostics/OTEL: add bounded outbound message delivery lifecycle diagnostics and export them as low-cardinality delivery spans/metrics without message body, recipient, room, or media-path data. (#71471) Thanks @vincentkoc and @jlapenna.</li>
|
||||
<li>Diagnostics/OTEL: emit bounded exec-process diagnostics and export them as <code>openclaw.exec</code> spans without exposing command text, working directories, or container identifiers. (#71451) Thanks @vincentkoc and @jlapenna.</li>
|
||||
<li>Diagnostics/OTEL: support <code>OPENCLAW_OTEL_PRELOADED=1</code> so the plugin can reuse an already-registered OpenTelemetry SDK while keeping OpenClaw diagnostic listeners wired. (#71450) Thanks @vincentkoc and @jlapenna.</li>
|
||||
<li>Providers/Xiaomi: add MiMo TTS as a bundled speech provider with MP3/WAV output and voice-note Opus transcoding. Fixes #52376. (#55614) Thanks @zoujiejun.</li>
|
||||
<li>Providers/ElevenLabs: include <code>eleven_v3</code> in the bundled TTS model catalog so model selection surfaces can offer ElevenLabs v3. (#68321) Thanks @itsuzef.</li>
|
||||
<li>Providers/Local CLI TTS: add a bundled local command speech provider with file/stdout input, voice-note Opus conversion, and telephony PCM output. (#56239) Thanks @solar2ain.</li>
|
||||
<li>Providers/Inworld: add Inworld as a bundled speech provider with streaming TTS synthesis, voice listing, voice-note output, and PCM telephony output. (#55972) Thanks @cshape.</li>
|
||||
<li>Providers/Volcengine: add Volcengine/BytePlus Seed Speech as a bundled TTS provider with API-key auth, native Ogg/Opus voice-note output, and MP3 audio-file output. (#55641) Thanks @xuruiray.</li>
|
||||
<li>Android/Talk Mode: expose Talk Mode in the Voice tab with runtime-owned voice capture modes and microphone foreground-service escalation. Thanks @alex-latitude.</li>
|
||||
<li>Providers/LiteLLM: register <code>litellm</code> as an image-generation provider so <code>image_generate model=litellm/...</code> calls and <code>agents.defaults.imageGenerationModel.fallbacks</code> entries resolve through the LiteLLM proxy. Thanks @zqchris.</li>
|
||||
<li>Providers/fal: add Seedance 2.0 reference-to-video models with multi-image, video, and audio reference input mapping plus model-specific capability limits for <code>video_generate</code>. Thanks @shivanker.</li>
|
||||
<li>Codex harness: require Codex app-server <code>0.125.0</code> or newer and cover native MCP <code>PreToolUse</code>, <code>PostToolUse</code>, and <code>PermissionRequest</code> payloads through the OpenClaw hook relay.</li>
|
||||
<li>Agents/Codex: teach prompts and <code>agents_list</code> to surface native Codex app-server availability so agents prefer <code>/codex ...</code> over Codex ACP unless ACP/acpx is explicit. Thanks @vincentkoc.</li>
|
||||
<li>ACPX/Droid: add Factory Droid to the live ACP bind Docker matrix, including <code>.factory</code> settings staging, <code>FACTORY_API_KEY</code> forwarding, and the single-agent <code>test:docker:live-acp-bind:droid</code> recipe.</li>
|
||||
<li>TTS/personas: add provider-aware TTS personas with deterministic provider binding merges, <code>/tts persona</code> controls, gateway/CLI persona state, Google Gemini <code>audio-profile-v1</code> prompt wrapping, and OpenAI instruction mapping. (#70748) Thanks @barronlroth.</li>
|
||||
<li>Voice Wake: add trigger-based routing so macOS voice wake phrases can select a configured agent or session target, with Gateway routing APIs and node update events. (#30354) Thanks @longbiaochen.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Auto-reply: poison inbound message dedupe after replay-unsafe provider/runtime failures so retries stay safe before visible progress but cannot duplicate messages after block output, tool side effects, or session progress. Fixes #69303; keeps #58549 and #64606 as duplicate validation. Thanks @martingarramon, @NikolaFC, and @zeroth-blip.</li>
|
||||
<li>Logging/sessions: apply configured redaction patterns to persisted session transcript text and accept escaped character classes in safe custom redaction regexes, so transcript JSONL no longer keeps matching sensitive text in the clear. Fixes #42982. Thanks @panpan0000.</li>
|
||||
<li>Agents/OpenAI: keep Responses web search compatible with minimal thinking by raising <code>web_search</code> requests to the lowest supported reasoning effort instead of sending a rejected minimal payload.</li>
|
||||
<li>Agents/tools: honor the <code>bundle-mcp</code> allowlist token when deciding whether bundled MCP tools are available, so restricted tool policies can still enable bundled MCP without exposing unrelated tools.</li>
|
||||
<li>Agents/model fallback: jump directly to a known later live-session model redirect instead of walking unrelated fallback candidates, while preserving the already-landed live-session/fallback loop guard. Fixes #57471; related loop family already closed via #58496. Thanks @yuxiaoyang2007-prog.</li>
|
||||
<li>Skills/memory: restore Chokidar v5 hot reloads by watching concrete skill and memory roots with filters, including SKILL.md removals and deleted skill folders without broad workspace recursion. Fixes #27404, #33585, and #41606. Thanks @shelvenzhou, @08820048, and @rocke2020.</li>
|
||||
<li>Discord/gateway: count failed health-monitor restart attempts toward cooldown and hourly caps, and evict stale account lifecycle state during channel reloads so repeated Discord gateway recovery cannot loop on old status. Fixes #38596. (#40413) Thanks @jellyAI-dev and @vashquez.</li>
|
||||
<li>Plugins/CLI: let flag-driven <code>openclaw channels add</code> install the selected channel plugin from its default source without opening an interactive prompt, fixing published npm Telegram setup in stdin-closed automation.</li>
|
||||
<li>Plugins/startup: load the default <code>memory-core</code> slot during Gateway startup when permitted so active-memory recall can call <code>memory_search</code> and <code>memory_get</code> without requiring an explicit <code>plugins.slots.memory</code> entry, while preserving <code>plugins.slots.memory: "none"</code>.</li>
|
||||
<li>Plugins/install: materialize plugin-owned root chunks in external bundled-runtime mirrors so staged plugin dependencies resolve under native ESM in packaged installs. Fixes #72058; supersedes #72084. Thanks @amnesia106 and @drvoss.</li>
|
||||
<li>Plugins/CLI: prefer native require for compiled bundled plugin JavaScript before jiti so read-only config, status, device, and node commands avoid unnecessary transform overhead on slow hosts. Fixes #62842. Thanks @Effet.</li>
|
||||
<li>Plugins/compat: inventory doctor-side deprecation migrations separately from runtime plugin compatibility so release sweeps preserve needed repairs while enforcing dated removal windows. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/compat: add missing dated compatibility records for legacy extension-api, memory registration, provider hook/type aliases, runtime aliases, channel SDK helpers, and approval/test utility shims. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/CLI: refresh the persisted registry after managed plugin files are removed so ClawHub uninstall cannot leave stale <code>plugins list</code> entries.</li>
|
||||
<li>Plugins/CLI: make plugin install and uninstall config writes conflict-aware, clear stale denylist entries on explicit reinstall/removal, and delete managed plugin files only after config/index commit succeeds.</li>
|
||||
<li>Plugins: fail <code>plugins update</code> when tracked plugin or hook updates error, keep bundled runtime-dependency repair behind restrictive allowlists, and reject package installs with unloadable extension entries.</li>
|
||||
<li>Gateway/chat: keep duplicate attachment-backed <code>chat.send</code> retries with the same idempotency key on the documented in-flight path so aborts still target the real active run. Fixes #70139. Thanks @Feelw00.</li>
|
||||
<li>Plugins: share package entrypoint resolution between install and discovery, reject mismatched <code>runtimeExtensions</code>, and cache bundled runtime-dependency manifest reads during scans.</li>
|
||||
<li>WhatsApp/Web: keep quiet but healthy linked-device sessions connected by basing the watchdog on WhatsApp Web transport activity, while retaining a longer app-silence cap so frame activity cannot mask a stuck session forever. Fixes #70678; carries forward the focused #71466 approach and keeps #63939 as related configurable-timeout follow-up. Thanks @vincentkoc and @oromeis.</li>
|
||||
<li>Onboarding/setup: keep first-run config reads, plugin compatibility notices, and post-model sanity checks on cold metadata paths unless the user chooses to browse all models, avoiding full plugin/runtime catalog work between prompts. Thanks @shakkernerd.</li>
|
||||
<li>Onboarding/auth: run manifest-owned provider auth choices through scoped setup providers so selecting OpenAI Codex browser/device auth no longer loads every provider runtime before OAuth starts. Thanks @shakkernerd.</li>
|
||||
<li>Onboarding/auth: keep the post-auth default-model policy lookup on manifest/setup metadata so the next prompt appears without loading broad provider runtime. Thanks @shakkernerd.</li>
|
||||
<li>Onboarding/models: keep skip-auth and provider-scoped model picker prompts off the full global model catalog path, and cache provider catalog hook resolution so setup no longer stalls after auth on large plugin registries. Thanks @shakkernerd.</li>
|
||||
<li>Gateway/Bonjour: suppress known @homebridge/ciao cancellation and network assertion failures through scoped process handlers so malformed mDNS packets or restricted VPS networking disable/restart Bonjour instead of crashing the gateway. Fixes #67578. Thanks @zenassist26-create.</li>
|
||||
<li>Discord: keep late clicks on already-resolved exec approval buttons quiet when elevated mode auto-resolved the request, while still surfacing real approval submission failures. Fixes #66906. Thanks @rlerikse.</li>
|
||||
<li>Agents/subagents: deliver completed yielded-subagent results back to no-thread requester routes via direct fallback when the dormant parent announce turn produces no visible reply, and add QA-lab coverage for the regression. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/Tailscale: let Tailscale-authenticated Control UI operator sessions with browser device identity skip the device-pairing round trip while still rejecting device-less and node-role connections. Refs #71986. Thanks @jokedul.</li>
|
||||
<li>Doctor: honor <code>OPENCLAW_SERVICE_REPAIR_POLICY=external</code> by reporting gateway service health while skipping service install/start/restart/bootstrap, supervisor rewrites, and legacy service cleanup for externally managed environments. Thanks @shakkernerd.</li>
|
||||
<li>CLI/update: run package post-update doctor with <code>--fix</code> so package updates repair config migrations before restart. Thanks @shakkernerd.</li>
|
||||
<li>CLI/update: retry failed npm global updates with <code>--omit=optional</code> and ignore the superseded first failure when the fallback succeeds. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/uninstall: migrate and reset <code>plugins.slots.contextEngine</code> alongside memory slots when plugin ids change or selected plugins are removed. Thanks @shakkernerd.</li>
|
||||
<li>Agents/Discord: keep raw <code>Agent failed before reply</code> runner failures out of Discord group/channel chats and show detailed runner errors in direct chats only when <code>/verbose</code> is enabled.</li>
|
||||
<li>UI/Windows: quote resolved pnpm <code>.cmd</code> launcher paths before spawning UI install/build/test commands so Node installs under <code>C:\Program Files</code> no longer fail as <code>C:\Program</code>. Fixes #45275. Thanks @Kobevictor, @stoppieboy, and @iubns.</li>
|
||||
<li>Codex/agent: translate <code>--thinking minimal</code> to <code>low</code> for modern Codex models (gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.2) at request build time so the first turn is accepted instead of paying a wasted call + retry-with-low fallback. Older Codex models still receive <code>minimal</code> directly. Fixes #71946. Thanks @hclsys.</li>
|
||||
<li>Plugins/uninstall: remove tracked plugin files from their recorded managed extensions root even when the current state directory points somewhere else, so <code>openclaw plugins uninstall --force</code> does not leave the plugin discoverable. Thanks @shakkernerd.</li>
|
||||
<li>Agents/runtime: add <code>agentRuntime.id</code> as the canonical config key, migrate legacy runtime-policy configs with <code>openclaw doctor --fix</code>, route canonical Anthropic models through <code>claude-cli</code> without passing CLI backend aliases to embedded harness selection, and load CLI backend owner plugins before channel startup. Fixes #71957. Thanks @WolvenRA.</li>
|
||||
<li>CLI/update: guard Windows scheduled-task stops by state and timeout so auto-update restart cannot hang indefinitely on <code>schtasks /End</code> before stale-listener cleanup. Fixes #69970. Thanks @yangswld and @sherlock-huang.</li>
|
||||
<li>Windows install/Lobster: execute <code>pnpm.exe</code> directly when <code>npm_execpath</code> points at the native pnpm binary, add an installed-package fallback for the Lobster embedded runtime, and include the Lobster runner regression test in Windows CI. Fixes #69456. Thanks @igormf.</li>
|
||||
<li>Gateway/install: refresh loaded gateway service installs when the current service embeds stale gateway auth instead of returning already-installed, avoiding LaunchAgent token-mismatch loops after token rotation. Fixes #70752. Thanks @hyspacex.</li>
|
||||
<li>Update: ignore bundled plugin <code>.openclaw-install-stage</code> directories during global install verification and packaged dist pruning so leftover runtime-dep staging files do not turn successful updates into <code>unexpected packaged dist file</code> failures. Fixes #71752. Thanks @waynegault.</li>
|
||||
<li>CLI/update: fail package updates when post-update plugin sync fails and refresh legacy npm plugin install records before trusting unchanged artifacts, preventing successful updates from restarting with stale or failed plugin state. Thanks @vincentkoc and @shakkernerd.</li>
|
||||
<li>Release/update: reject pre-populated bundled plugin <code>.openclaw-install-stage</code> directories, including mixed-case path variants, before package inventory generation so release tarballs cannot ship poisoned runtime-dependency staging debris. Fixes #71752. Thanks @hclsys.</li>
|
||||
<li>Node runtime: keep node-host retry timers alive across Gateway restarts and exit on terminal credential pauses so supervised nodes do not become silent zombies. Fixes #69800. Thanks @meroli28.</li>
|
||||
<li>Gateway/plugins: stop persisted WhatsApp auth state from activating bundled channel runtime-dependency repair during startup when <code>channels.whatsapp</code> is absent, avoiding npm/git stalls on packaged Linux installs. Fixes #71994. Thanks @xiao398008.</li>
|
||||
<li>Gateway/device tokens: enforce caller-scope containment inside token rotation and revocation so pairing-only sessions cannot mutate higher-scope operator tokens. Fixes #71990. Thanks @coygeek.</li>
|
||||
<li>Plugins/channels: keep security checks, thread-binding placement, provider summaries, health formatting, and message action labels on read-only or already-loaded channel metadata instead of importing full channel runtime. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/status: keep config-only channel labels and status security summaries from importing plugin runtime modules just to render metadata. Thanks @shakkernerd.</li>
|
||||
<li>Sessions/channels: stop group-session metadata from loading bundled channel runtime just to classify <code>#channel</code> subjects, using only already-loaded channel capabilities on that path. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/channels: keep native command and native skill <code>auto</code> defaults on static channel metadata so config, audit, and command-list checks do not load channel runtime just to read those defaults. Thanks @shakkernerd.</li>
|
||||
<li>CLI/channels: keep channel remove selection and all-channel capabilities summaries on read-only plugin metadata, loading channel runtime only for the selected mutation path. Thanks @shakkernerd.</li>
|
||||
<li>CLI/models: keep Provider Index preview rows out of <code>models list --all --provider <id></code> when the owning provider plugin is disabled, preserving config authority for cold catalog fallbacks. Thanks @shakkernerd.</li>
|
||||
<li>CLI/model runs: keep <code>openclaw infer model run</code> on explicit OpenRouter models from loading the full provider catalog or inheriting chat-agent silent-reply policy, restoring non-empty one-shot probe output. Fixes #68791. Thanks @limpredator.</li>
|
||||
<li>Installer/macOS: rerun Homebrew install steps without the gum spinner when raw-mode ioctl failures occur, and avoid claiming <code>node@24</code> was installed when the Homebrew keg binary is missing. Fixes #70411. Thanks @1fanwang and @dad-io.</li>
|
||||
<li>Installer: load nvm before Node.js detection so <code>curl | bash</code> installs respect nvm-managed Node instead of stale system Node. Fixes #49556. Thanks @heavenlxj.</li>
|
||||
<li>Installer/Windows: route PowerShell install failures through a top-level handler so <code>iwr ... | iex</code> returns control to the current shell while direct script-file runs still exit non-zero. Fixes #38054. Thanks @PwrSrg.</li>
|
||||
<li>CLI/Volta: respawn raw <code>openclaw</code> CLI runs through the named <code>node</code> shim when the current Node executable resolves to <code>volta-shim</code>, avoiding direct shim execution failures in non-interactive shells. Fixes #68672. Thanks @sanchezm86.</li>
|
||||
<li>Installer: warn when multiple npm global roots contain OpenClaw installs, showing active Node/npm/openclaw plus each install path and version so stale version-manager installs are visible. Fixes #40839. Thanks @zhixianio.</li>
|
||||
<li>Cron/tasks: recover completed cron task ledger records from durable run logs and job state before marking them <code>lost</code>, reducing false <code>backing session missing</code> audit errors for isolated cron runs and keeping offline CLI audit from treating its empty local cron active-job set as authoritative. Fixes #71963.</li>
|
||||
<li>Docker: copy patched dependency files into runtime images so downstream <code>pnpm install</code> layers keep working. Fixes #69224. Thanks @gucasbrg.</li>
|
||||
<li>Package: include patched dependency files in the published npm package so downstream installs can resolve <code>patchedDependencies</code>. (#69224) Thanks @gucasbrg and @vincentkoc.</li>
|
||||
<li>Plugins/channels: treat malformed bundled channel plugin loaders that return <code>undefined</code> as unavailable instead of crashing config and help paths. Fixes #69044. Thanks @frankhli843 and @vincentkoc.</li>
|
||||
<li>Scripts/watch: show corrupted dependency package-config recovery guidance when <code>gateway:watch</code> fails during watcher startup, without double-logging unrelated import failures. (#58780) Thanks @roytong9 and @vincentkoc.</li>
|
||||
<li>Signal: read signal-cli RPC, health checks, and SSE events through Node's HTTP client so Node 24/25 fetch regressions do not break Signal sends or inbound events. Fixes #51716 and #53040. Thanks @Barukimang, @minupla, and @vincentkoc.</li>
|
||||
<li>Skills/Docker: run npm-backed skill dependency installs with an OpenClaw-managed user prefix so non-root Docker images do not write to <code>/usr/local</code>. Fixes #59601. Thanks @chanjarster and @vincentkoc.</li>
|
||||
<li>Agents/runtime: submit heartbeat, cron, and exec wakeups as transient runtime context instead of visible user prompts, keeping synthetic system work out of chat transcripts. Fixes #66496 and #66814. Thanks @jeades and @mandomaker.</li>
|
||||
<li>Telegram: include native quote excerpts automatically for threaded replies and reply tags when the original Telegram text is available, without adding another config knob. Fixes #6975. Thanks @rex05ai.</li>
|
||||
<li>Node/Linux: make <code>openclaw node install</code> enable and restart the <code>openclaw-node</code> systemd unit instead of the gateway unit on node-only VMs. Fixes #68287. Thanks @dlebee-agent.</li>
|
||||
<li>Browser/CDP: retry transient raw-CDP WebSocket handshake failures before any browser command is sent, and reconnect stale persistent Playwright CDP sessions for safe tab-list reads without replaying mutating browser actions. Fixes #67728.</li>
|
||||
<li>Gateway/Linux: retry <code>systemctl --user enable</code> after a second daemon reload when the freshly written gateway unit is not visible yet on migrated systemd installs. Fixes #65184. Thanks @liushuaiiu.</li>
|
||||
<li>Telegram: preserve exact selected quote text when sending native quote replies, and retry with legacy replies if Telegram rejects quote parameters. (#71952) Thanks @rubencu.</li>
|
||||
<li>Plugins/CLI: preserve manifest name, description, format, and source metadata in cold <code>openclaw plugins list</code> output without importing plugin runtime. Thanks @shakkernerd.</li>
|
||||
<li>Security/audit: read channel exposure and plugin allowlist ownership from read-only plugin index metadata so cold audits do not depend on loaded channel runtime. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/chat: keep <code>/plugins list</code>, <code>/plugins enable</code>, and <code>/plugins disable</code> on the persisted plugin index path so chat plugin management does not load diagnostic/runtime plugin registries before execution. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/doctor: read workspace plugin status and legacy web-search ownership through installed-index manifest metadata instead of broad manifest registry scans. Thanks @shakkernerd.</li>
|
||||
<li>CLI/agents: read channel provider status from read-only plugin index metadata for text <code>agents list</code> output instead of the loaded channel registry. Thanks @shakkernerd.</li>
|
||||
<li>Logging: redact configured secret patterns at console and file-log sink exits so credentials that reach the logger are masked before terminal display or JSONL persistence. Fixes #67953. Thanks @Ziy1-Tan.</li>
|
||||
<li>Gateway/services: refuse process and service mutations from an older OpenClaw binary when the config was last written by a newer version, preventing split-brain installs from stopping or rewriting newer gateway services. Fixes #57079.</li>
|
||||
<li>Gateway: reserve <code>/healthz</code> and <code>/readyz</code> ahead of plugin, canvas, and Control UI HTTP stages so liveness/readiness probes still answer when a later route handler stalls. Fixes #69674. Thanks @Xike-Creek.</li>
|
||||
<li>Logging: load <code>logging.file</code> and redaction settings directly from the active OpenClaw config path in bundled runtimes, so packaged gateways stop falling back to <code>/tmp/openclaw</code>. Fixes #59370, #67168, and #61295. Thanks @KeaneYan, @Pan9hu, and @zsjlovelike.</li>
|
||||
<li>Logging: rotate file logs at <code>logging.maxFileBytes</code>, keep bounded numbered archives, and make long-lived rolling loggers follow the current-day file instead of suppressing diagnostics or writing stale dated files. Fixes #58583 and #62381. Thanks @jpeghead and @zhaoleink.</li>
|
||||
<li>Agents/groups: treat clean empty assistant stops as silent <code>NO_REPLY</code> only for always-on groups where silent replies are allowed, while keeping direct and mention-gated sessions on the incomplete-turn retry path. Thanks @MagnaAI.</li>
|
||||
<li>macOS/Node: keep native remote app nodes from advertising <code>browser.proxy</code>, start browser-capable CLI node services through the restored <code>openclaw node start</code> command, and show an actionable browser-control error when the local control service is missing. Fixes #66637.</li>
|
||||
<li>Gateway/update: fail package updates when the restarted managed gateway reports the wrong version, including fallback restarts and JSON mode, avoiding false-success mixed-version restarts after macOS LaunchAgent updates. Fixes #71835. Thanks @abhinas90 and @jsompis.</li>
|
||||
<li>Gateway/update: warn before package updates and bundled plugin runtime-dependency repairs when the target volume appears low on disk space, without blocking installs on best-effort filesystem checks. Fixes #71835. Thanks @abhinas90 and @jsompis.</li>
|
||||
<li>Plugins/runtime deps: surface activated plugin load failures in health and fail package-update restart verification or doctor repair when bundled runtime deps still cannot load, avoiding false-success repairs. (#71883) Thanks @Solvely-Colin.</li>
|
||||
<li>Gateway/Linux: include fnm <code>aliases/default/bin</code> in generated service PATHs and let doctor accept either modern fnm aliases or the legacy <code>current/bin</code> symlink, avoiding false PATH repair prompts. Fixes #68169. Thanks @richard-scott.</li>
|
||||
<li>Installer/Linux: run apt installs with noninteractive dpkg and needrestart settings so fresh Ubuntu 24.04 <code>curl | bash</code> installs do not hang while installing Node.js, Git, or build tools. Fixes #41146. Thanks @iht76, @alexcarv318, @cs3gallery, @firofame, and @cgdusek.</li>
|
||||
<li>Providers/Bedrock: defer the AWS SDK import until Bedrock discovery actually runs so plugin registration and setup stay lightweight on cold start. Fixes #71690. Thanks @jarvis-ai-gregmoser.</li>
|
||||
<li>Installer/macOS: stop immediately when Homebrew <code>node@24</code> installation fails and avoid printing PATH advice for missing Homebrew Node installs. Fixes #70411. Thanks @1fanwang.</li>
|
||||
<li>WhatsApp: remove ack reactions after a visible reply when <code>messages.removeAckAfterReply</code> is enabled, matching other reaction-capable channels. Fixes #26183. Thanks @MrUnforsaken.</li>
|
||||
<li>Providers/Z.AI: map OpenClaw thinking controls to Z.AI's <code>thinking</code> payload and add opt-in preserved thinking replay via <code>params.preserveThinking</code>, so GLM 5.x can keep prior <code>reasoning_content</code> when requested. Fixes #58680. Thanks @xuanmingguo.</li>
|
||||
<li>Channels/status: keep read-only channel lists on manifest and package metadata by default, loading setup runtime only for explicit fallback callers. Thanks @shakkernerd.</li>
|
||||
<li>Plugins: scope setup and web-provider metadata manifest reads to explicit plugin ids when callers already know the owning plugin set. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/onboarding: defer onboarding install-record index writes until the guarded config commit so setup failures cannot leave the plugin index ahead of <code>openclaw.json</code>. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/registry: resolve web provider ownership from the installed plugin index instead of broad manifest scans on secret, tool, and pricing paths. Thanks @shakkernerd.</li>
|
||||
<li>Config/providers: accept <code>video</code> and <code>audio</code> in configured model <code>input</code> values and preserve them in provider catalog entries. Fixes #20721. Thanks @alvinttang.</li>
|
||||
<li>Models/auth: honor the parent <code>--agent</code> flag for auth write commands (<code>add</code>, <code>login</code>, <code>setup-token</code>, <code>paste-token</code>, and the GitHub Copilot shortcut) so OAuth/API-key/token results are written to the requested agent store instead of the default agent. Fixes #71864. (#71933) Thanks @balric-seo.</li>
|
||||
<li>TTS: strip model-emitted TTS directives from streamed block text before channel delivery, including directives split across adjacent blocks, while preserving the accumulated raw reply for final-mode synthesis. Fixes #38937.</li>
|
||||
<li>TTS: keep explicit <code>provider=...</code> directive keys scoped to that provider and warn on unsupported keys instead of letting another speech provider consume overlapping keys. Fixes #60131.</li>
|
||||
<li>TTS/Feishu: normalize final-mode streamed TTS-only audio before delivery so generated voice-note files use the same safe media path and native voice routing as normal final replies. Fixes #71920.</li>
|
||||
<li>Feishu: transcribe inbound voice-note audio with the shared media audio path before agent dispatch and keep raw Feishu <code>file_key</code> payloads out of message text. Fixes #67120 and #61876.</li>
|
||||
<li>Tasks: terminalize async Gateway agent task records from the Gateway run result while preserving aborted, failed, and cancelled outcomes instead of leaving completed runs stuck as active or lost. (#71905) Thanks @likewen-tech.</li>
|
||||
<li>WhatsApp: let authorized group voice-note transcripts satisfy mention gating before reply dispatch, while keeping unmentioned transcripts in pending group history. Fixes #44908.</li>
|
||||
<li>Media understanding: carry channel voice-note preflight state into attachment selection so WhatsApp, Feishu, Telegram, and Discord do not transcribe the same inbound audio twice. Fixes #70580.</li>
|
||||
<li>TTS/BlueBubbles: deliver compatible auto-TTS audio as iMessage voice memo bubbles instead of plain MP3/CAF file attachments. Fixes #16848.</li>
|
||||
<li>TTS: resolve voice-note and voice-memo routing from channel plugin capabilities instead of speech-core-owned channel id lists.</li>
|
||||
<li>ACP: send subagent and async-task completion wakes to external ACP harnesses as plain prompts instead of OpenClaw internal runtime-context envelopes, while keeping those envelopes out of ACP transcripts.</li>
|
||||
<li>TTS/status: show configured TTS model, voice, and sanitized custom endpoint in <code>/status</code>, preserve OpenAI-compatible TTS instructions on custom endpoints, and retry empty Microsoft/Edge TTS output once. Addresses #46602, #47232, and #43936. Thanks @leekuangtao, @Huntterxx, and @rex993.</li>
|
||||
<li>Agents/Gateway: steer agent-driven config edits and restarts through the owner-only <code>gateway</code> tool, document <code>config.schema.lookup</code> as the field-doc source, and warn against using <code>gateway stop && gateway start</code> as a restart substitute on macOS. Fixes #71929. Thanks @ygc3817922006-sketch.</li>
|
||||
<li>Media understanding/audio: inject a deterministic transcript placeholder for too-small voice notes so agents do not hallucinate transcription or provider failures. Fixes #48944. Thanks @eulicesl.</li>
|
||||
<li>Providers/vLLM: send Nemotron 3 chat-template kwargs when thinking is off and honor configured <code>params.chat_template_kwargs</code> for OpenAI-compatible completions, so vLLM/Nemotron replies stay visible instead of becoming thinking-only. Fixes #71891. Thanks @jmystaki-create and @dennis-lynch.</li>
|
||||
<li>Channels/replies: strip copied inbound metadata blocks from user-facing assistant replies and model replay history, so Discord/vLLM sessions do not leak <code>Conversation info</code> / <code>UNTRUSTED ... message body</code> envelopes after a model echoes them. Fixes #71847. Thanks @jmystaki-create.</li>
|
||||
<li>Subagents/memory: keep inter-session completion wakes out of memory and dreaming session exports, and strip internal runtime-context blocks from realtime Control UI chat events.</li>
|
||||
<li>Agents/Claude: treat zero-token empty <code>stop</code> turns as failed provider output, retry once, repair replay, and allow configured model fallback instead of preserving them as successful silent replies. Fixes #71880. Thanks @MagnaAI.</li>
|
||||
<li>Tasks: normalize task lifecycle timestamps at create, update, and restore time, and report retained lost tasks as audit warnings until their cleanup window expires. (#71871) Thanks @likewen-tech.</li>
|
||||
<li>Diagnostics/OTEL: treat normal early model stream cleanup as a completed model call instead of exporting a misleading <code>StreamAbandoned</code> error span. Thanks @vincentkoc.</li>
|
||||
<li>Gateway/pairing: stop corrupt or unreadable device/node pairing stores from being treated as empty state, preserving <code>paired.json</code> for repair instead of overwriting approved pairings. Fixes #71873. Thanks @iret77.</li>
|
||||
<li>ACP: keep <code>/acp</code> management commands, plus local <code>/status</code> and <code>/unfocus</code>, on the Gateway path inside ACP-bound threads so they are not consumed as ACP prompt text. Fixes #66298. Thanks @kindomLee.</li>
|
||||
<li>ACPX: stop probing ACP agents during normal Gateway startup; the embedded backend now registers without spawning Codex/ACP child processes unless <code>OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=1</code> is explicitly set.</li>
|
||||
<li>CLI/image edit: accept <code>--size</code>, <code>--aspect-ratio</code>, and <code>--resolution</code> on <code>openclaw infer image edit</code> and report all supported edit flags from <code>capability inspect image.edit</code>. Thanks @Pinghuachiu.</li>
|
||||
<li>ACP: wait for the configured runtime backend to become healthy before startup identity reconciliation, avoiding transient acpx warnings during Gateway boot. Fixes #40566.</li>
|
||||
<li>Channels/ACP bindings: time out configured binding readiness checks instead of letting Discord preflight hang forever when an ACP target never settles. Fixes #68776.</li>
|
||||
<li>Control UI: hide the chat loading skeleton during background history reloads when existing messages or active stream content are already visible, avoiding reload flashes on high-latency local gateways. Fixes #71844. Thanks @WolvenRA.</li>
|
||||
<li>Control UI: keep locally optimistic chat messages visible when a history reload temporarily returns empty, avoiding lost first-turn messages on high-latency gateways. Fixes #71878. Thanks @WolvenRA.</li>
|
||||
<li>Control UI: keep chat history limits based on visible messages after filtering heartbeat and control-only transcript rows, so recent hidden entries no longer make older visible replies disappear. Thanks @WolvenRA.</li>
|
||||
<li>Agents/images: scrub old <code>[media attached: ...]</code>, <code>[Image: source: ...]</code>, and <code>media://inbound/...</code> markers from pruned model replay context so stale media refs are not rehydrated as fresh prompt images. Fixes #71868. Thanks @jmeadlock.</li>
|
||||
<li>Docker/Bonjour: disable Bonjour/mDNS advertising by default for bundled Compose gateways on bridge networking, while keeping host/macvlan opt-in with <code>OPENCLAW_DISABLE_BONJOUR=0</code>. Fixes #71879. Thanks @gbballpack.</li>
|
||||
<li>CLI/status: label the OpenClaw Serve/Funnel setting as <code>Tailscale exposure</code> and show daemon state separately when available, so <code>gateway.tailscale.mode: "off"</code> no longer reads like the Tailscale daemon is stopped. Fixes #71790. Thanks @pesvobodak.</li>
|
||||
<li>Plugins/Bonjour: stop ciao mDNS watchdog failures from looping forever when the advertiser stays stuck in <code>probing</code> or <code>announcing</code>; Bonjour now disables itself for the current Gateway process after repeated failed restarts while the Gateway keeps running. Fixes #69011. Thanks @siddharthaagarwalofficial-ux, @FiredMosquito831, and @spikefcz.</li>
|
||||
<li>Gateway/Fly.io: seed Control UI allowed origins from the actual runtime bind and port so CLI-driven non-loopback starts do not crash before config exists. Fixes #71823.</li>
|
||||
<li>macOS/remote SSH: keep discovered gateway hosts in <code>gateway.remote.sshTarget</code> while pinning SSH transport URLs to the local loopback tunnel, so browser automation does not regress into blocked non-loopback <code>ws://</code> endpoints. Fixes #67336.</li>
|
||||
<li>Gateway/proxy: bootstrap env proxy dispatching from direct Gateway startup so provider and plugin network requests honor <code>HTTPS_PROXY</code>/<code>HTTP_PROXY</code> before the first embedded agent attempt runs. (#71833) Thanks @mjamiv.</li>
|
||||
<li>Plugins/runtime deps: verify clean npm installs actually place requested bundled runtime packages in the managed install root, reporting exact missing specs instead of a false successful repair. (#71883) Thanks @Solvely-Colin.</li>
|
||||
<li>Plugins/discovery: ignore stale <code>plugins.load.paths</code> aliases that point back at packaged bundled plugin directories and have doctor remove them, keeping bundled plugins on the runtime-deps staging path.</li>
|
||||
<li>Models/LM Studio: preserve <code>@iq*</code> quant suffixes in model refs and provider matching so <code>/model lmstudio/...@iq3_xxs</code> keeps the exact LM Studio variant. Fixes #71474. (#71486) Thanks @Bartok9, @XinwuC, and @Sanjays2402.</li>
|
||||
<li>Matrix/cron: preserve the live Matrix delivery target when creating implicit announce reminder jobs so mixed-case room IDs are not reconstructed from lowercased session keys. Fixes #71798.</li>
|
||||
<li>Feishu: accept Schema 2.0 card action callbacks that report <code>context.open_chat_id</code> instead of legacy <code>context.chat_id</code>, so button callbacks no longer drop as malformed. Fixes #71670. Thanks @eddy1068.</li>
|
||||
<li>Feishu: keep synthetic card-action and bot-menu ids out of platform reply targets, using the real card callback message id when Feishu provides one and plain-sending otherwise. Fixes #71673. Thanks @eddy1068.</li>
|
||||
<li>Plugins/QQ Bot: prefer an installed QQ Bot plugin that declares it replaces the bundled <code>qqbot</code> channel, preventing duplicate <code>qqbot_channel_api</code> and <code>qqbot_remind</code> tool registration noise. Fixes #63102.</li>
|
||||
<li>Browser automation: keep stable tab ids and labels attached when Chromium replaces the raw target after form submissions or other action-triggered navigations, and return the replacement <code>targetId</code> from <code>/act</code> when the match is provable. Fixes #46137.</li>
|
||||
<li>QQ Bot: make <code>qqbot_remind</code> schedule, list, and remove Gateway cron jobs directly for owner-authorized senders instead of returning <code>cronParams</code> and relying on a follow-up generic <code>cron</code> tool call. Fixes #70865. (#70937) Thanks @GaosCode.</li>
|
||||
<li>Agents/ACP: hide <code>sessions_spawn</code> ACP runtime options unless an ACP backend is loaded, and make <code>/acp doctor</code> call out <code>plugins.allow</code> blocking bundled <code>acpx</code>. Thanks @vincentkoc.</li>
|
||||
<li>Agents/Codex: keep ACP prompt/skill routing hidden unless an ACP runtime backend is available, and warn in doctor when enabled Codex plugin configs still route <code>openai-codex/*</code> models through PI. Thanks @vincentkoc.</li>
|
||||
<li>Media delivery: avoid sending generated image attachments twice when the assistant reply already includes explicit <code>MEDIA:</code> lines for the same turn, and reject unsafe remote <code>MEDIA:</code> URLs before delivery. Thanks @pashpashpash.</li>
|
||||
<li>Codex harness: ignore retryable app-server error notifications after Codex recovers, and preserve the real nested error message for terminal app-server failures instead of replacing it with a generic failure. Thanks @pashpashpash.</li>
|
||||
<li>Agents/Codex: prepare native Codex sub-agent session metadata without a nested Gateway session patch and add a focused Docker smoke for the app-server sub-agent path. Thanks @vincentkoc.</li>
|
||||
<li>Agents/subagents: keep queued subagent announces session-only when the requester has no external channel target, avoiding ambiguous multi-channel delivery failures. Fixes #59201. Thanks @larrylhollan.</li>
|
||||
<li>Image understanding: preserve configured provider-prefixed vision model metadata when callers request the model without the provider prefix, so custom image models keep their <code>input: ["text", "image"]</code> capability. Fixes #33185. Thanks @Kobe9312 and @vincentkoc.</li>
|
||||
<li>Plugins/install: restore the previous plugin index records if a concurrent config write conflict interrupts install, update, or uninstall metadata commits. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: reject native plugin archives that do not include a valid <code>openclaw.plugin.json</code>, preventing manifestless archives from writing install records that later show missing-manifest diagnostics. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/uninstall: remove tracked managed plugin install directories even when the persisted install path differs from the default id-derived target, while still refusing deletes outside the managed extensions root. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/update: restore previous plugin index records if core update or channel setup hits a concurrent config write conflict after plugin metadata changes. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/onboarding: defer channel/provider plugin install records until the owning config write commits, keeping setup failures from advancing the plugin index ahead of <code>openclaw.json</code>. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/config: route configure and agent setup writes with pending plugin install records through the plugin index commit helper so provider onboarding metadata is not stripped by plain config writes. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/channels: merge pending channel plugin install records with the existing plugin index before config writes, preserving unrelated tracked installs during channel setup, resolve, remove, and capability repair flows. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/config: defer shipped <code>plugins.installs</code> index migration during config writes until the guarded config commit window and roll it back if the config write fails before commit. Thanks @shakkernerd.</li>
|
||||
<li>Sessions: keep embedded runtime context out of the visible user prompt by sending it as a hidden next-turn custom message, and teach doctor to repair affected 2026.4.24 transcripts with duplicated prompt-rewrite branches. Fixes #71761.</li>
|
||||
<li>Gateway/subagents: keep direct-loopback backend RPCs authenticated with the shared gateway token/password off stale CLI paired-device scope baselines, so internal calls no longer hit <code>scope-upgrade</code> pairing prompts while remote, browser, node, device-token, and explicit-device paths still require normal pairing approval. Fixes #63548.</li>
|
||||
<li>Providers/Azure OpenAI: give deployment-scoped image generation requests a longer 600s default timeout so slow <code>gpt-image-2</code> generations can complete without a per-call <code>timeoutMs</code>. Fixes #71705. Thanks @voytas75.</li>
|
||||
<li>Gateway/plugins: link source-checkout bundled runtime dependency caches instead of recursively copying <code>node_modules</code> on the gateway main thread, preventing local status, node, and skill probes from timing out during startup cache restores.</li>
|
||||
<li>Skills/remote nodes: only expose remote macOS skill bins for connected nodes, clear stale bin matches when node probes fail, and include probe command, timeout, bin count, and connection state in timeout logs.</li>
|
||||
<li>Skills/remote nodes: recognize <code>system.which</code> object-map responses when probing connected macOS nodes, so Linux gateways can expose macOS-only skills such as Apple Notes when the required binaries are installed remotely. Fixes #71877. Thanks @miguelarios.</li>
|
||||
<li>CLI/gateway: keep diagnostic probes from creating first-time read-only device pairings, while still reusing cached device tokens for detailed read probes. Fixes #71766. Thanks @SunboZ.</li>
|
||||
<li>CLI/plugins: keep <code>message</code> startup, <code>channels logs</code>, <code>agents delete</code>, and <code>agents set-identity</code> off broad plugin preloading; message delivery still loads plugins when the action actually runs.</li>
|
||||
<li>Image understanding: resolve configured image models such as local LM Studio vision entries before reporting <code>Unknown model</code> when the discovery registry has not registered that provider. Fixes #66486. Thanks @zhanggpcsu.</li>
|
||||
<li>QQ Bot: ignore self-echoed bot messages using the outbound ref-index marker, preventing mirrored replies from re-entering the agent loop while still allowing users to quote bot replies. Fixes #71912. Thanks @wangyc6003.</li>
|
||||
<li>Sessions: separate reset freshness from session-store <code>updatedAt</code>, so heartbeat, cron, exec, and gateway bookkeeping no longer prevent configured daily/idle resets from rolling long-running channel sessions. Fixes #68315, #63732, #63820, and #69083. Thanks @maxatv, @longhairedsi, @bradfreels, and @akessel56.</li>
|
||||
<li>Sessions: clear queued system-event notices during <code>/new</code>, <code>/reset</code>, gateway <code>sessions.reset</code>, and daily/idle rollover so stale background updates cannot leak into the first prompt of the fresh session. Fixes #66864. Thanks @opeyio, @Magicray1217, and @cedillarack.</li>
|
||||
<li>CLI/agents: keep <code>agents bind</code>, <code>agents unbind</code>, and <code>agents bindings</code> on setup-safe channel metadata paths so they do not preload bundled plugin runtimes or stage runtime dependencies. Fixes #71743.</li>
|
||||
<li>Plugins/registry: preserve explicit disabled plugin records during registry migration without persisting every unused bundled plugin discovered on disk. Thanks @shakkernerd.</li>
|
||||
<li>Windows/native: keep CLI startup and bundled provider plugin loading off Windows ESM raw-path failure paths, fixing native onboarding/install smoke on Node 24.</li>
|
||||
<li>Plugins/doctor: read bundled channel doctor capabilities through the same packaged plugin directory resolver used by plugin loading, so published installs keep Matrix DM allowlist repairs on <code>channels.matrix.dm.*</code> instead of writing invalid top-level <code>dmPolicy</code> keys. Fixes #71757.</li>
|
||||
<li>Plugins/Windows: keep bundled plugin Jiti loaders off the native import path on Windows so channel plugins such as Telegram no longer crash with <code>ERR_UNSUPPORTED_ESM_URL_SCHEME</code> on <code>C:\...</code> paths. Fixes #71749. Thanks @smeyer9.</li>
|
||||
<li>Providers/Ollama: use Ollama's current <code>/api/web_search</code> endpoint and honor <code>https://ollama.com</code> model-provider base URLs for Ollama Web Search. Fixes #71741. Thanks @madhvidua.</li>
|
||||
<li>Memory/Ollama: serialize Ollama memory embedding batches and add an inline batch timeout override, with longer defaults for local/self-hosted embedding providers.</li>
|
||||
<li>Sessions/usage: exclude compaction checkpoint transcript snapshots from usage totals and session discovery, while keeping old checkpoint files removable.</li>
|
||||
<li>CLI/agents: keep <code>openclaw agents list --json</code> on the config-only path by default, avoiding bundled plugin loading unless callers request <code>--bindings</code>. Fixes #71739. Thanks @kaloster.</li>
|
||||
<li>Plugins/install: force plugin dependency installs to stay project-local even when inherited npm config requests global installs, so successful installs still materialize the plugin's staged <code>node_modules</code>.</li>
|
||||
<li>Providers/Google: transcode Gemini TTS PCM to Opus for voice-note targets so WhatsApp and other native voice-note replies can play as voice messages.</li>
|
||||
<li>TTS/WhatsApp: mark non-Opus provider output as voice-note intent so channel delivery transcodes MP3/WebM replies to Ogg/Opus PTT audio.</li>
|
||||
<li>Plugins/runtime deps: reuse existing external bundled-plugin stage roots when mirrored plugin roots are inspected again, avoiding second-generation <code>openclaw-unknown-*</code> stages and repeated first-turn restaging. Fixes #71599.</li>
|
||||
<li>iOS/macOS Talk Mode: allow <code>talk.speechLocale</code> to set the speech recognition locale for non-English voice conversations. Fixes #44688.</li>
|
||||
<li>Plugins/providers: honor explicit plugin candidate lists instead of reading a persisted registry snapshot from local state, keeping candidate-scoped provider discovery hermetic.</li>
|
||||
<li>Plugins/doctor: keep bundled plugin runtime-dependency repairs inside the managed OpenClaw stage even when user npm prefix/global config points npm at <code>$HOME/node_modules</code>. Fixes #71730.</li>
|
||||
<li>ACP/sessions_spawn: reject normal OpenClaw config agent ids when callers explicitly request <code>runtime="acp"</code>, while allowing agents configured with <code>runtime.type="acp"</code> to resolve to their ACP harness id. Fixes #63914.</li>
|
||||
<li>ACP/sessions_spawn: apply <code>runTimeoutSeconds</code> to ACP child turns and dispatch those turns on the background subagent lane, so quota-stalled ACP harnesses do not occupy the main agent lane indefinitely. Fixes #68823.</li>
|
||||
<li>ACP/oneshot: reconcile runtime session identity before closing completed oneshot ACP runs, so finished <code>sessions.json</code> entries do not stay stuck with <code>acp.identity.state="pending"</code>.</li>
|
||||
<li>ACPX: bundle <code>acpx@0.6.1</code> so unsupported generic model overrides fail clearly instead of silently falling back to the target adapter default.</li>
|
||||
<li>ACP/models: document that non-Codex ACP model overrides require adapter support for ACP <code>models</code> plus <code>session/set_model</code>, so unsupported harnesses fail clearly instead of silently falling back to their defaults.</li>
|
||||
<li>Plugins/Voice Call: treat missing provider credentials as setup-incomplete during Gateway startup and log the missing keys as a warning instead of a runtime startup error, while keeping explicit command/tool errors when used.</li>
|
||||
<li>Android/Talk Mode: prevent duplicate TTS playback when fast or repeated final chat events arrive while Talk Mode is waiting for its own response. Fixes #46546.</li>
|
||||
<li>Tooling/check:changed: pass parent heavy-check lock markers to lint lanes so <code>pnpm check:changed</code> no longer waits on its own <code>lint:extensions</code> child.</li>
|
||||
<li>CLI/completion: dedupe provider auth flags before registering <code>openclaw onboard</code> options, so completion-cache refresh during update no longer fails when stale core fallback flags overlap plugin manifest flags. Fixes #71667.</li>
|
||||
<li>Diagnostics/trace: report live context usage from the current prompt snapshot instead of provider turn totals, avoiding false near-full context spikes on cached or tool-heavy runs.</li>
|
||||
<li>Providers/Google: honor <code>models.providers.google.request.allowPrivateNetwork</code> for Gemini TTS and telephony TTS, matching Google image generation and media understanding. (#71723) Thanks @ro-hansolo.</li>
|
||||
<li>Providers/MiniMax: register <code>minimax-portal</code> for music and video generation, preserving OAuth auth and regional MiniMax base URLs across the shared <code>music_generate</code> and <code>video_generate</code> tools. (#63241) Thanks @tars90percent.</li>
|
||||
<li>Providers/onboarding: keep Runway and Alibaba Model Studio out of the text-inference setup picker by scoping their video-generation auth choices to the media setup flow. (#65856) Thanks @Jah-yee.</li>
|
||||
<li>Plugins/Bonjour: stop the gateway from crash-looping on <code>CIAO PROBING CANCELLED</code> when the mDNS watchdog cancels a stuck probe. Restores the rejection-handler wiring dropped during the bonjour plugin migration and shares unhandled-rejection state across module instances so plugin-staged copies of <code>openclaw/plugin-sdk/runtime</code> register into the same handler set the host consults. Especially affects Docker on macOS, where mDNS probing reliably hits the watchdog. Thanks @troyhitch.</li>
|
||||
<li>Google Meet: report pinned Chrome nodes as offline or missing capabilities in setup/join diagnostics, keep inaccessible nodes out of auto-selection, and preflight local BlackHole/SoX requirements before agents try local Chrome.</li>
|
||||
<li>Providers/MiniMax: route <code>image-01</code> requests to the dedicated image generation endpoint while preserving CN endpoint selection. Fixes #61149. Thanks @mushuiyu886.</li>
|
||||
<li>Plugins/startup: remove ownerless bundled runtime-dependency install locks after a short grace window and include lock owner details when startup times out waiting for a plugin runtime-deps lock.</li>
|
||||
<li>Plugins/install: anchor bundled runtime-dependency npm installs with an OpenClaw-owned package manifest so Linux updates cannot accidentally write to a parent <code>$HOME/node_modules</code> tree. Fixes #71730.</li>
|
||||
<li>Plugins/install: pass onboarding plugin config into plugin index writes so local plugin installs outside default discovery roots keep their install records. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: migrate shipped <code>plugins.installs</code> config records into the plugin index while stripping them from runtime config and future writes. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: durably remove shipped <code>plugins.installs</code> from <code>openclaw.json</code> after its records are copied into the plugin index, while rolling back the index write if config cleanup fails. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/install: keep migrated plugin install records in the plugin index even when the plugin manifest is missing or invalid, so update, uninstall, inspect, and audit can still recover broken installs. Thanks @shakkernerd.</li>
|
||||
<li>Plugins/security: keep plugin audit JSON check ids stable while reporting plugin index install-record findings with updated wording. Thanks @shakkernerd.</li>
|
||||
<li>CLI/config: reject direct <code>plugins.installs</code> edits with guidance to use <code>openclaw plugins install</code>, <code>openclaw plugins update</code>, or <code>openclaw plugins uninstall</code> instead. Thanks @shakkernerd.</li>
|
||||
<li>Live tests/voice: accept common STT variants for OpenClaw and ElevenLabs brand names so provider smoke tests fail on real regressions rather than equivalent transcripts.</li>
|
||||
<li>Agents/replies: forward sanitized underlying agent failure details on external channels instead of replacing unknown failures with a generic retry message.</li>
|
||||
<li>CLI/MCP: translate OpenClaw <code>mcp.servers.*.transport</code> entries into Claude/Gemini CLI <code>type</code> fields so streamable HTTP MCP servers load in CLI backend sessions. (#71724) Thanks @Blockchain-Oracle.</li>
|
||||
<li>Browser/CDP: honor configured remote and <code>attachOnly</code> CDP HTTP/WebSocket timeouts when opening tabs through raw CDP or <code>/json/new</code> fallback. (#54238) Thanks @FuncWei.</li>
|
||||
<li>WhatsApp/TTS: send visible text separately from PTT voice-note audio instead of relying on hidden voice-note captions. Fixes #51081.</li>
|
||||
<li>Browser/client: avoid telling agents to restart OpenClaw for dispatcher timeouts on external browser profiles such as <code>attachOnly</code>, remote CDP, and existing-session. (#40815) Thanks @0xsline.</li>
|
||||
<li>Agents/TTS: preserve <code>[[audio_as_voice]]</code> directives on trusted text tool-result <code>MEDIA:</code> payloads so generated audio still delivers as a voice note. (#46535) Thanks @azade-c.</li>
|
||||
<li>Agents/TTS: keep queued tool media when an assistant ends with <code>NO_REPLY</code> on non-block delivery paths, so media-only generated audio replies still send. (#60025) Thanks @bradlind1.</li>
|
||||
<li>Telegram/STT: frame inbound voice-note transcripts as machine-generated, untrusted text in agent context while preserving raw transcript mention detection. Closes #33360. Thanks @smartchainark.</li>
|
||||
<li>Subagents/browser: show an actionable <code>/tools</code> notice when browser automation is configured but filtered out by the active tool profile, and document that coding-profile agents should use <code>tools.alsoAllow: ["browser"]</code> rather than subagent allowlists alone.</li>
|
||||
<li>Control UI/Quick Settings: persist the assistant avatar override to browser local storage (mirroring the user avatar) so uploaded image data URLs no longer fail config validation with "Too big: expected string to have <=200 characters". Also lift the gateway-side <code>ui.assistant.avatar</code> length cap to match the user avatar size budget for non-UI clients writing the field directly. Thanks @BunsDev.</li>
|
||||
<li>Plugin SDK: share diagnostic event subscriptions across duplicate source/dist module graphs so legacy root SDK imports still receive runtime diagnostic events.</li>
|
||||
<li>Agents/Bedrock: prevent empty assistant stream-error turns from poisoning Converse replay by persisting, repairing, and replaying a non-empty fallback block. Fixes #71572. (#71627) Thanks @openperf.</li>
|
||||
<li>Agents/Anthropic/Bedrock: strip thinking blocks with missing, empty, or blank replay signatures before provider conversion, falling back to non-empty omitted-reasoning text when needed so corrupted signed-thinking history no longer poisons subsequent turns. Fixes #45010. (#70054) Thanks @castaples.</li>
|
||||
<li>Agents/Anthropic/Bedrock: preserve stripped thinking-only assistant replay turns with non-empty omitted-reasoning text so provider adapters keep strict user/assistant turn shape. Thanks @wujiaming88.</li>
|
||||
<li>ACP/Codex: pass <code>sessions_spawn(runtime="acp")</code> model and thinking overrides into Codex ACP startup, normalize <code>openai-codex/*</code> refs and slash reasoning suffixes, and recognize managed Codex ACP wrapper commands without blocking current <code>gpt-5.5</code> sessions. Fixes #40393. (#71643) Thanks @91wan.</li>
|
||||
<li>Browser/CDP: make readiness diagnostics use the same discovery-first fallback as reachability for bare <code>ws://</code> Browserless and Browserbase CDP URLs. Fixes #69532.</li>
|
||||
<li>Browser/CDP: explain that loopback Browserless or other externally managed CDP services need <code>attachOnly: true</code> and matching Browserless <code>EXTERNAL</code> endpoint when reporting local port ownership conflicts, and fall back to the configured bare WebSocket root when a discovered Browserless endpoint rejects CDP. Fixes #49815.</li>
|
||||
<li>Gateway/reload: preserve indefinite <code>gateway.reload.deferralTimeoutMs: 0</code> semantics for channel hot reload deferrals so active agent runs are not interrupted by a forced channel restart. (#71637) Thanks @Poo-Squirry.</li>
|
||||
<li>Agents/tool results: cap persisted Pi tool-result details and strip hidden diagnostics before provider conversion, preventing large debug payloads from bloating session transcripts. (#71637) Thanks @Poo-Squirry.</li>
|
||||
<li>ACP/OpenCode: update the bundled acpx runtime to 0.6.0 and cover the OpenCode ACP bind path in Docker live tests.</li>
|
||||
<li>Providers/OpenCode Go: add DeepSeek V4 Pro and DeepSeek V4 Flash to the Go catalog while the bundled Pi registry catches up. Fixes #71587.</li>
|
||||
<li>Providers/OpenCode Go: route DeepSeek V4 Pro/Flash through the OpenAI-compatible Go endpoint and suppress invalid <code>reasoning_effort: "off"</code> payloads, fixing tool-enabled requests for <code>opencode-go/deepseek-v4-flash</code>. Fixes #71683.</li>
|
||||
<li>Plugins/model defaults: run Skill Workshop review, Active Memory recall, and session-memory slug generation on the configured agent default model instead of the hardcoded OpenAI SDK fallback when hook context lacks model metadata. Fixes #71659.</li>
|
||||
<li>Providers/Venice: fill the required DeepSeek V4 <code>reasoning_content</code> placeholder for <code>venice/deepseek-v4-pro</code> and <code>venice/deepseek-v4-flash</code> replay turns without sending native DeepSeek <code>thinking</code> controls that Venice rejects. Fixes #71628.</li>
|
||||
<li>Browser/existing-session: support per-profile Chrome MCP command/args, map <code>cdpUrl</code> to <code>--browserUrl</code> or <code>--wsEndpoint</code>, and avoid combining endpoint flags with <code>--userDataDir</code>. Fixes #47879, #48037, and #62706. Thanks @puneet1409, @zhehao, and @madkow1001.</li>
|
||||
<li>Media/plugins: bound MIME sniffing and ZIP archive preflight before handing untrusted files to <code>file-type</code> or <code>jszip</code>, reducing parser CPU and memory exposure for attachments and ClawHub plugin archives. Thanks @vincentkoc.</li>
|
||||
<li>Memory-host SDK: use trusted env-proxy mode for remote embedding and batch HTTP calls only when Undici will proxy that target, preserving SSRF DNS pinning for <code>ALL_PROXY</code>-only and <code>NO_PROXY</code> bypass cases. Fixes #52162. (#71506) Thanks @DhtIsCoding.</li>
|
||||
<li>Gateway/dashboard: render Control UI and WebSocket links with <code>https://</code>/<code>wss://</code> when <code>gateway.tls.enabled=true</code>, including <code>openclaw gateway status</code>. Fixes #71494. (#71499) Thanks @deepkilo.</li>
|
||||
<li>Agents/OpenAI-compatible: default proxy/local completions tool requests to <code>tool_choice: "auto"</code> when tools are present, so providers enter native tool-calling mode instead of replying with plain-text tool directives. (#71472) Thanks @Speed-maker.</li>
|
||||
<li>OpenAI image generation: use <code>gpt-5.5</code> for the Codex OAuth responses transport instead of the retired <code>gpt-5.4</code> model, fixing 500s from ChatGPT Codex image generation. Fixes #71513. Thanks @baolongl.</li>
|
||||
<li>OpenAI image generation: route transparent-background default-model requests to <code>gpt-image-1.5</code>, document the expected <code>image_generate</code> call shape, and keep Azure/custom OpenAI-compatible deployment names untouched.</li>
|
||||
<li>Google video generation: download direct MLDev Veo <code>video.uri</code> results instead of passing them through the Files API path, fixing 404s after successful generation/polling. Fixes #71200. Thanks @panhaishan.</li>
|
||||
<li>Google video generation: fall back to the REST <code>predictLongRunning</code> Veo endpoint for text-only SDK 404s while keeping reference image/video generation on the SDK path. Fixes #62309 and #63008. (#62343) Thanks @leoleedev.</li>
|
||||
<li>MiniMax music generation: switch the bundled default model from the unsupported <code>music-2.5+</code> id to the current <code>music-2.6</code> API model. Fixes #64870 and addresses the music default from #62315. Thanks @noahclanman and @edwardzheng1.</li>
|
||||
<li>Cron: record jobs interrupted by a gateway restart as failed at their original <code>runningAtMs</code>, skip unsafe startup replay, and disable interrupted one-shot jobs so they show a visible failure instead of silently disappearing or duplicating work. Fixes #59056, #61343, #63657, and #59301. Thanks @ponchoooPenguin, @daemic24, @myradon, and @hikiwibot.</li>
|
||||
<li>Cron tool: recover flat top-level schedule shorthand such as <code>cron</code>, <code>tz</code>, and <code>staggerMs</code> before gateway validation, so model-generated cron add/update calls preserve cron jitter settings. Thanks @tyxben.</li>
|
||||
<li>Cron: hydrate flat legacy job rows with top-level <code>cron</code>, <code>tz</code>, <code>session</code>, and <code>message</code> fields into canonical schedule, target, and payload objects before startup recomputes run times. Fixes #43351.</li>
|
||||
<li>Agents/replies: let pending group chat history trigger bare mentioned turns without treating metadata-only inbound context as user input. Fixes #71489. (#71520) Thanks @SymbolStar.</li>
|
||||
<li>Google media generation: strip a configured trailing <code>/v1beta</code> from Google music/video provider base URLs before calling the Google GenAI SDK, preventing doubled <code>/v1beta/v1beta</code> paths. Fixes #63240. (#63258) Thanks @Hybirdss.</li>
|
||||
<li>Discord: restore direct-message voice-note preflight transcription and classify URL-only Ogg/Opus voice attachments as audio while skipping partial attachments without usable URLs. Fixes #61314 and #64803.</li>
|
||||
<li>Plugins/build: copy bundled plugin skill trees into <code>dist-runtime</code>, broaden Windows symlink-copy fallbacks, and fingerprint runtime dependencies from <code>lstat</code> so symlink-like directory entries cannot crash staging.</li>
|
||||
<li>Google Chat: preserve reply text when a typing indicator message is deleted or can no longer be updated, so media captions and first text chunks are resent instead of silently disappearing. (#71498) Thanks @colin-lgtm.</li>
|
||||
<li>Cron: tolerate malformed legacy job rows in startup, main-session system-event payloads, and human-readable <code>cron list</code> output so missing <code>state</code>, <code>payload.text</code>, or display fields no longer crash the scheduler or CLI. Fixes #66016, #65916, #64137, #57872, #59968, #63813, #52804, and #43163. (#71509) Thanks @vincentkoc.</li>
|
||||
<li>CLI/models: make <code>openclaw models scan</code> fall back to public OpenRouter free-model metadata when no <code>OPENROUTER_API_KEY</code> is configured, avoid config secret resolution for explicit <code>--no-probe</code> scans, and apply the scan timeout to the OpenRouter catalog request.</li>
|
||||
<li>Feishu: keep streaming cards to one live card per turn, flush throttled card edits after meaningful text boundaries, and skip exact block/partial repeats so tool-heavy replies do not duplicate card output. Thanks @allan0509.</li>
|
||||
<li>Feishu: finish the streaming-card duplicate closeout by stripping leaked reasoning tags, preserving cross-block partial snapshots, enabling topic-thread streaming cards, omitting the generic <code>main</code> card header, surfacing transient tool/compaction status, and cleaning streaming state after close failures. Thanks @sesame437, @Vicky-v7, @maoku-family, @Pengxiao-Wang, and @Maple778.</li>
|
||||
<li>Telegram: recover incomplete partial-stream previews by falling back to a final send when an ambiguous final edit failure would otherwise retain a strict prefix of the answer. Fixes #71525. (#71554) Thanks @sahilsatralkar.</li>
|
||||
<li>Control UI/chat: collapse assistant token/model context details behind an explicit Context disclosure and show full dates in message footers, making historical transcript timing clear without noisy default metadata. (#71337) Thanks @BunsDev.</li>
|
||||
<li>OpenAI/Codex OAuth: explain <code>unsupported_country_region_territory</code> token-exchange failures with a proxy/region hint instead of surfacing a generic OAuth error. Fixes #51175. (#71501) Thanks @vincentkoc and @wulala-xjj.</li>
|
||||
<li>Browser/Linux: fall back to headless mode for local managed profiles on hosts without a display server, while preserving explicit per-profile headed overrides and reporting the headless source. (#60953) Thanks @rrpsantos.</li>
|
||||
<li>Telegram: remove the startup persisted-offset <code>getUpdates</code> preflight so polling restarts do not self-conflict before the runner starts. Fixes #69304. (#69779) Thanks @chinar-amrutkar.</li>
|
||||
<li>Telegram: keep the polling stall watchdog active even when grammY reports the runner as not running while its task is still pending, so a rebuilt transport cannot leave <code>getUpdates</code> silent until a manual gateway restart. Fixes #69064. Thanks @LDLoeb.</li>
|
||||
<li>Subagents: fall back to direct completion delivery when the parent announce turn finishes without a visible payload, so child results still reach channel-backed requester sessions.</li>
|
||||
<li>Subagents: tell parent agents to use <code>sessions_yield</code> while waiting for child completion events, preventing GPT-5 fast runs from ending silently after spawning workers.</li>
|
||||
<li>Browser/Playwright: ignore benign already-handled route races during guarded navigation so browser-page tasks no longer fail when Playwright tears down a route mid-flight. (#68708) Thanks @Steady-ai.</li>
|
||||
<li>Browser/CLI: lazy-load browser command groups and plugin runtime services so <code>openclaw browser --help</code> can render without loading the full browser automation stack. Fixes #65400. (#65460, #66640) Thanks @pandego and @Tianworld.</li>
|
||||
<li>Browser/CLI: serve precomputed <code>openclaw browser --help</code> text from CLI startup metadata, avoiding the full plugin/config startup path for the common help invocation.</li>
|
||||
<li>Browser/downloads: seed managed Chrome profiles with OpenClaw download prefs and capture unmanaged click-triggered downloads under the guarded downloads directory, while explicit download waiters still own their target file. (#64558) Thanks @Pearcekieser.</li>
|
||||
<li>Browser/Chrome: stop passing redundant <code>--disable-setuid-sandbox</code> when <code>browser.noSandbox</code> is enabled; <code>--no-sandbox</code> remains the effective sandbox opt-out. (#67939) Thanks @sebykrueger.</li>
|
||||
<li>Browser/client: stop telling agents to permanently avoid the browser after transient timeout or cancellation failures; keep the no-retry hint for persistent unavailable/rate-limit cases. (#46505) Thanks @jriff.</li>
|
||||
<li>Browser/aria snapshots: bind <code>format=aria</code> <code>axN</code> refs to live DOM nodes through backend DOM ids when Playwright is available, so follow-up browser actions can use those refs without timing out. (#62434) Thanks @MrKipler.</li>
|
||||
<li>Telegram: prevent duplicate in-process long pollers for the same bot token and add clearer <code>getUpdates</code> conflict diagnostics for external duplicate pollers. Fixes #56230. Thanks @Co-Messi.</li>
|
||||
<li>Browser/Linux: detect Chromium-based installs under <code>/opt/google</code>, <code>/opt/brave.com</code>, <code>/usr/lib/chromium</code>, and <code>/usr/lib/chromium-browser</code> before asking users to set <code>browser.executablePath</code>. (#48563) Thanks @lupuletic.</li>
|
||||
<li>Sessions/browser: close tracked browser tabs when idle, daily, <code>/new</code>, or <code>/reset</code> session rollover archives the previous transcript, preventing tabs from leaking past the old session. Thanks @jakozloski.</li>
|
||||
<li>Sessions/forking: fall back to transcript-estimated parent token counts when cached totals are stale or missing, so oversized thread forks start fresh instead of cloning the full parent transcript. Thanks @jalehman.</li>
|
||||
<li>OpenAI/Codex: send Codex Responses system prompts through top-level <code>instructions</code> while preserving the existing native Codex payload controls.</li>
|
||||
<li>MCP/CLI: retire bundled MCP runtimes at the end of one-shot <code>openclaw agent</code> and <code>openclaw infer model run</code> gateway/local executions, so repeated scripted runs do not accumulate stdio MCP child processes. Fixes #71457. Thanks @spartoviMD.</li>
|
||||
<li>OpenAI/Codex image generation: canonicalize legacy <code>openai-codex.baseUrl</code> values such as <code>https://chatgpt.com/backend-api</code> to the Codex Responses backend before calling <code>gpt-image-2</code>, matching the chat transport. Fixes #71460. Thanks @GodsBoy.</li>
|
||||
<li>Control UI: make <code>/usage</code> use the fresh context snapshot for context percentage, and include cache-write tokens in the Usage overview cache-hit denominator. Fixes #47885. Thanks @imwyvern and @Ante042.</li>
|
||||
<li>GitHub Copilot: preserve encrypted Responses reasoning item IDs during replay so Copilot can validate encrypted reasoning payloads across requests. (#71448) Thanks @a410979729-sys.</li>
|
||||
<li>GitHub Copilot: never rewrite connection-bound reasoning item IDs regardless of whether <code>encrypted_content</code> is present, fixing a 400 "Encrypted content item_id did not match" error with <code>gpt-5.3-codex</code> and future Codex models that fall through to the forward-compat catch-all with <code>reasoning: false</code>. Also recognize Codex-named models as reasoning-capable so they inherit the correct capability flags. Refs #68735. Thanks @InvalidPandaa.</li>
|
||||
<li>Agents/replies: recover final-answer text when streamed assistant chunks contain only whitespace, preventing completed turns from surfacing as empty-payload errors. Fixes #71454. (#71467) Thanks @Sanjays2402.</li>
|
||||
<li>Feishu/TTS: transcode voice-intent MP3 and other audio replies to Ogg/Opus before sending native Feishu audio bubbles, while keeping ordinary MP3 attachments as files. Fixes #61249 and #37868. Thanks @sg1416-zg and @ycjlb2023-peteryi.</li>
|
||||
<li>WhatsApp/TTS: transcode MP3/WebM audio, including Microsoft Edge TTS output, to Ogg/Opus before sending PTT voice notes.</li>
|
||||
<li>QQBot/TTS: honor plain <code>audioAsVoice</code> replies by synthesizing TTS to native QQ voice messages, and mark inbound voice-only messages as audio media without exposing raw voice paths to generic media context.</li>
|
||||
<li>Providers/SenseAudio: add bundled SenseAudio batch audio transcription through <code>tools.media.audio</code> with <code>SENSEAUDIO_API_KEY</code> auth. (#66943) Thanks @Fl0rencess720.</li>
|
||||
<li>Providers/MiniMax: let TTS use MiniMax portal OAuth and Token Plan credentials before falling back to <code>MINIMAX_API_KEY</code>, and include current TTS HD model ids. Fixes #55017. Thanks @zx15210404690-hash.</li>
|
||||
<li>Telegram/webhook: acknowledge validated webhook updates before running bot middleware, keeping slow agent turns from tripping Telegram delivery retries while preserving per-chat processing lanes. Fixes #71392. Thanks @joelforsberg46-source.</li>
|
||||
<li>MCP/config reload: hot-apply <code>mcp.*</code> changes by disposing cached session MCP runtimes, and dispose bundled MCP runtimes during gateway shutdown so removed <code>mcp.servers</code> entries reap child processes promptly. Fixes #60656. Thanks @xieyuanqing.</li>
|
||||
<li>Active Memory: keep silent recall sub-agent billing/auth failures out of shared auth-profile cooldown state, so a Claude CLI extra-usage rejection cannot disable normal Claude-backed turns. Fixes #71284. (#71539) Thanks @vishutdhar and @obviyus.</li>
|
||||
<li>Auth/Claude CLI: sync refreshed Claude CLI OAuth credentials into the managed auth profile so long-running Claude CLI runs stop falling back to stale OpenClaw snapshots. (#70902) Thanks @starvex.</li>
|
||||
<li>Sessions: make <code>sessions_spawn(mode="session")</code> errors name usable alternatives when the current channel cannot bind subagent threads. Fixes #67400. (#67790) Thanks @stainlu.</li>
|
||||
<li>Agents/Claude CLI: pass the OpenClaw system prompt through Claude's prompt-file flag so Windows runs avoid argv length failures without changing system prompt semantics. Fixes #69158. (#69211) Thanks @skylee-01, @cassioanorte, @Syu0, and @Stache73.</li>
|
||||
<li>Agents/CLI sessions: bind <code>google-gemini-cli</code> session auth-epoch to the Google account identity in <code>~/.gemini/oauth_creds.json</code>, so Gemini-backed agents resume their conversation after gateway restart instead of minting a fresh session, and stale bindings are invalidated when the authenticated Google account changes. Fixes #70973. (#71076) Thanks @openperf.</li>
|
||||
<li>Slack: stop treating user mentions in assistant-authored message edit blocks as sender attribution, preventing edited bot messages from spoofing a mentioned DM user. (#71700) Thanks @vincentkoc.</li>
|
||||
<li>Codex: consume unauthorized bound conversation inbound claims before they can fall through to other claim handlers or enqueue Codex turns. (#71702) Thanks @vincentkoc.</li>
|
||||
<li>Codex media understanding: require approval-checked app-server image turns while explicitly declining tool, file, permission, and elicitation approval requests for the bounded image worker. (#71703) Thanks @vincentkoc.</li>
|
||||
<li>Agents/Claude CLI: allow large live <code>stream-json</code> JSONL lines up to the existing per-turn raw limit, preventing large Telegram, WebChat, MCP, and image turns from aborting on the old stdout buffer cap. Fixes #71793, #71080, and #70766. (#71897) Thanks @chacher86, @shivamgrover21, and @tpjordan.</li>
|
||||
<li>Agents/Claude CLI: unwrap nested Claude result envelopes in CLI JSON output so delegated agent responses surface as final text instead of raw result JSON. (#66819) Thanks @mraleko.</li>
|
||||
<li>Agents/Claude CLI: apply the configured 1M context window override to eligible Claude CLI Opus and Sonnet models when <code>context1m</code> is enabled. (#70863) Thanks @bidadh.</li>
|
||||
<li>Models/status: report fresh Claude CLI native auth instead of stale stored <code>anthropic:claude-cli</code> profile expiry when local credentials are current. Fixes #71256. (#71332) Thanks @matthiasjanke and @neeravmakwana.</li>
|
||||
<li>CLI backends: compact OpenClaw transcripts after over-budget CLI turns and reseed fresh CLI sessions from the compacted transcript instead of stale external resume state. Fixes #68329. (#71916) Thanks @obviyus.</li>
|
||||
<li>Telegram: keep default tool progress messages visible when answer preview streaming is disabled. (#71825) Thanks @VACInc.</li>
|
||||
<li>Configure/models: clear deselected model fallbacks when updating the model picker allowlist, including provider-scoped setup flows. (#71596) Thanks @rubencu.</li>
|
||||
<li>Agents/streaming: strip namespaced <code><antml:thinking></code> reasoning tags from streamed assistant replies before user-visible text is emitted. (#69288) Thanks @xialonglee.</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.4.25/OpenClaw-2026.4.25.zip" length="48125363" type="application/octet-stream" sparkle:edSignature="RnQ01wCFgupauUdwOFan+XPGZhBJi/w3sgJYA5EaasbeGrduDHBGw1e9Zj2Lqb4ud8e6Q+tRcJVfxh5KKSEIDg=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.4.24</title>
|
||||
<pubDate>Sat, 25 Apr 2026 19:34:45 +0000</pubDate>
|
||||
@@ -689,5 +50,318 @@
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.4.24/OpenClaw-2026.4.24.zip" length="48033180" type="application/octet-stream" sparkle:edSignature="wxOfxadSZ/9iXMitaC6SA9J6YPZC3P2tkeK7HZPHzjUIlzQTvOl7EjR4aRyXzaYt1N1AK5ba+YhuCwEngrTdCQ=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.4.22</title>
|
||||
<pubDate>Thu, 23 Apr 2026 15:18:00 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026042290</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.4.22</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.4.22</h2>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Providers/xAI: add image generation, text-to-speech, and speech-to-text support, including <code>grok-imagine-image</code> / <code>grok-imagine-image-pro</code>, reference-image edits, six live xAI voices, MP3/WAV/PCM/G.711 TTS formats, <code>grok-stt</code> audio transcription, and xAI realtime transcription for Voice Call streaming. (#68694) Thanks @KateWilkins.</li>
|
||||
<li>Providers/STT: add Voice Call streaming transcription for Deepgram, ElevenLabs, and Mistral, alongside the existing OpenAI and xAI realtime STT paths; ElevenLabs also gains Scribe v2 batch audio transcription for inbound media.</li>
|
||||
<li>TUI: add local embedded mode for running terminal chats without a Gateway while keeping plugin approval gates enforced. (#66767) Thanks @fuller-stack-dev.</li>
|
||||
<li>Onboarding: auto-install missing provider and channel plugins during setup so first-run configuration can complete without manual plugin recovery.</li>
|
||||
<li>OpenAI/Responses: use OpenAI's native <code>web_search</code> tool automatically for direct OpenAI Responses models when web search is enabled and no managed search provider is pinned; explicit providers such as Brave keep the managed <code>web_search</code> tool.</li>
|
||||
<li>Models/commands: add <code>/models add <provider> <modelId></code> so you can register a model from chat and use it without restarting the gateway; keep <code>/models</code> as a simple provider browser while adding clearer add guidance and copy-friendly command examples. (#70211) Thanks @Takhoffman.</li>
|
||||
<li>WhatsApp: add configurable native reply quoting with replyToMode for WhatsApp conversations. Thanks @mcaxtr.</li>
|
||||
<li>WhatsApp/groups+direct: forward per-group and per-direct <code>systemPrompt</code> config into inbound context <code>GroupSystemPrompt</code> so configured per-chat behavioral instructions are injected on every turn. Supports <code>"*"</code> wildcard fallback and account-scoped overrides under <code>channels.whatsapp.accounts.<id>.{groups,direct}</code>; account maps fully replace root maps (no deep merge), matching the existing <code>requireMention</code> pattern. Closes #7011. (#59553) Thanks @Bluetegu.</li>
|
||||
<li>Agents/sessions: add mailbox-style <code>sessions_list</code> filters for label, agent, and search plus visibility-scoped derived title and last-message previews. (#69839) Thanks @dangoZhang.</li>
|
||||
<li>Control UI/settings+chat: add a browser-local personal identity for the operator (name plus local-safe avatar), route user identity rendering through the shared chat/avatar path used by assistant and agent surfaces, and tighten Quick Settings, agent fallback chips, and narrow-screen chat layouts so personalization no longer wastes space or clips controls. (#70362) Thanks @BunsDev.</li>
|
||||
<li>Gateway/diagnostics: enable payload-free stability recording by default and add a support-ready diagnostics export with sanitized logs, status, health, config, and stability snapshots for bug reports. (#70324) Thanks @gumadeiras.</li>
|
||||
<li>Providers/Tencent: add the bundled Tencent Cloud provider plugin with TokenHub onboarding, docs, <code>hy3-preview</code> model catalog entries, and tiered Hy3 pricing metadata. (#68460) Thanks @JuniperSling.</li>
|
||||
<li>Providers/Amazon Bedrock Mantle: add Claude Opus 4.7 through Mantle's Anthropic Messages route with provider-owned bearer-auth streaming, so the model is actually callable without treating AWS bearer tokens like Anthropic API keys. Thanks @wirjo.</li>
|
||||
<li>Providers/GPT-5: move the GPT-5 prompt overlay into the shared provider runtime so compatible GPT-5 models receive the same behavior and heartbeat guidance through OpenAI, OpenRouter, OpenCode, Codex, and other GPT providers; add <code>agents.defaults.promptOverlays.gpt5.personality</code> as the global friendly-style toggle while keeping the OpenAI plugin setting as a fallback.</li>
|
||||
<li>Providers/OpenAI Codex: remove the Codex CLI auth import path from onboarding and provider discovery so OpenClaw no longer copies <code>~/.codex</code> OAuth material into agent auth stores; use browser login or device pairing instead. (#70390) Thanks @pashpashpash.</li>
|
||||
<li>CLI/Claude: default <code>claude-cli</code> runs to warm stdio sessions, including custom configs that omit transport fields, and resume from the stored Claude session after Gateway restarts or idle exits. (#69679) Thanks @obviyus.</li>
|
||||
<li>Pi/models: update the bundled pi packages to <code>0.68.1</code> and let the OpenCode Go catalog come from pi instead of plugin-maintained model aliases, adding the refreshed <code>opencode-go/kimi-k2.6</code>, Qwen, GLM, MiMo, and MiniMax entries.</li>
|
||||
<li>Tokenjuice: add bundled native OpenClaw support for tokenjuice as an opt-in plugin that compacts noisy <code>exec</code> and <code>bash</code> tool results in Pi embedded runs. (#69946) Thanks @vincentkoc.</li>
|
||||
<li>ACPX: add an explicit <code>openClawToolsMcpBridge</code> option that injects a core OpenClaw MCP server for selected built-in tools, starting with <code>cron</code>.</li>
|
||||
<li>CLI/doctor plugins: lazy-load doctor plugin paths and prefer installed plugin <code>dist/*</code> runtime entries over source-adjacent JavaScript fallbacks, reducing the measured <code>doctor --non-interactive</code> runtime by about 74% while keeping cold doctor startup on built plugin artifacts. (#69840) Thanks @gumadeiras.</li>
|
||||
<li>CLI/debugging: add an opt-in temporary debug timing helper for local CLI performance investigations, with readable stderr output, JSONL capture, and docs for removing probes before landing fixes. (#70469) Thanks @shakkernerd.</li>
|
||||
<li>Docs/i18n: add Thai translation support for the docs site.</li>
|
||||
<li>Providers/OpenAI-compatible: mark known local backends such as vLLM, SGLang, llama.cpp, LM Studio, LocalAI, Jan, TabbyAPI, and text-generation-webui as streaming-usage compatible, so their token accounting no longer degrades to unknown/stale totals. (#68711) Thanks @gaineyllc.</li>
|
||||
<li>Providers/OpenAI-compatible: recover streamed token usage from llama.cpp-style <code>timings.prompt_n</code> / <code>timings.predicted_n</code> metadata and sanitize usage counts before accumulation, fixing unknown or stale totals when compatible servers do not emit an OpenAI-shaped <code>usage</code> object. (#41056) Thanks @xaeon2026.</li>
|
||||
<li>Plugins/startup: prefer native Jiti loading for built bundled plugin dist modules on supported runtimes, cutting measured bundled plugin load time by 82-90% while keeping source TypeScript on the transform path. (#69925) Thanks @aauren.</li>
|
||||
<li>Plugin SDK/STT: share realtime transcription WebSocket transport and multipart batch transcription form helpers across bundled STT providers, reducing provider plugin boilerplate while preserving proxy capture, reconnects, audio queueing, close flushing, upload filename normalization, and ready handshakes.</li>
|
||||
<li>Plugin SDK/Pi embedded runs: add a bundled-plugin embedded extension factory seam so native plugins can extend Pi embedded runs with async runtime hooks such as <code>tool_result</code> handling instead of falling back to the older synchronous persistence path. (#69946) Thanks @vincentkoc.</li>
|
||||
<li>Codex harness/hooks: route native Codex app-server turns through <code>before_prompt_build</code> and emit <code>before_compaction</code> / <code>after_compaction</code> for native compaction items so prompt and compaction hooks stop drifting from Pi. Thanks @vincentkoc.</li>
|
||||
<li>Codex harness/plugins: add a bundled-plugin Codex app-server extension seam for async <code>tool_result</code> middleware, fire <code>after_tool_call</code> for Codex tool runs, and route mirrored Codex transcript writes through <code>before_message_write</code> so tool integrations stop diverging from Pi. Thanks @vincentkoc.</li>
|
||||
<li>Codex harness/hooks: fire <code>llm_input</code>, <code>llm_output</code>, and <code>agent_end</code> for native Codex app-server turns so lifecycle hooks stop drifting from Pi. Thanks @vincentkoc.</li>
|
||||
<li>QA/Telegram: record per-scenario reply RTT in the live Telegram QA report and summary, starting with the canary response. (#70550) Thanks @obviyus.</li>
|
||||
<li>Status: add an explicit <code>Runner:</code> field to <code>/status</code> so sessions now report whether they are running on embedded Pi, a CLI-backed provider, or an ACP harness agent/backend such as <code>codex (acp/acpx)</code> or <code>gemini (acp/acpx)</code>. (#70595)</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Thinking defaults/status: raise the implicit default thinking level for reasoning-capable models from legacy <code>off</code>/<code>low</code> fallback behavior to a safe provider-supported <code>medium</code> equivalent when no explicit config default is set, preserve configured-model reasoning metadata when runtime catalog loading is empty, and make <code>/status</code> report the same resolved default as runtime.</li>
|
||||
<li>Gateway/model pricing: fetch OpenRouter and LiteLLM pricing asynchronously at startup and extend catalog fetch timeouts to 30 seconds, reducing noisy timeout warnings during slow upstream responses.</li>
|
||||
<li>Agents/sessions: keep daily reset and idle-maintenance bookkeeping from bumping session activity or pruning freshly active routes, so active conversations no longer look newer or disappear for maintenance-only updates.</li>
|
||||
<li>Plugins/install: add newly installed plugin ids to an existing <code>plugins.allow</code> list before enabling them, so allowlisted configs load installed plugins after restart.</li>
|
||||
<li>Status: show <code>Fast</code> in <code>/status</code> when fast mode is enabled, including config/default-derived fast mode, and omit it when disabled.</li>
|
||||
<li>OpenAI/image generation: detect Azure OpenAI-style image endpoints, use Azure <code>api-key</code> auth plus deployment-scoped image URLs, honor <code>AZURE_OPENAI_API_VERSION</code>, and document the Azure setup path so image generation and edits work against Azure-hosted OpenAI resources. (#70570) Thanks @zhanggpcsu.</li>
|
||||
<li>Telegram/forum topics: cache recovered forum metadata with bounded expiry so supergroup updates no longer need repeated <code>getChat</code> lookups before topic routing.</li>
|
||||
<li>Onboarding/WeCom: show the official WeCom channel plugin with its native Enterprise WeChat display name and blurb in the external channel catalog.</li>
|
||||
<li>Models/auth: merge provider-owned default-model additions from <code>openclaw models auth login</code> instead of replacing <code>agents.defaults.models</code>, so re-authenticating an OAuth provider such as OpenAI Codex no longer wipes other providers' aliases and per-model params. Migrations that must rename keys (Anthropic -> Claude CLI) opt in with <code>replaceDefaultModels</code>. Fixes #69414. (#70435) Thanks @neeravmakwana.</li>
|
||||
<li>Media understanding/audio: prefer configured or key-backed STT providers before auto-detected local Whisper CLIs, so installed local transcription tools no longer shadow API providers such as Groq/OpenAI in <code>tools.media.audio</code> auto mode. Fixes #68727.</li>
|
||||
<li>Providers/OpenAI: lock the auth picker wording for OpenAI API key, Codex browser login, and Codex device pairing so the setup choices no longer imply a mixed Codex/API-key auth path. (#67848) Thanks @tmlxrd.</li>
|
||||
<li>Agents/BTW: route <code>/btw</code> side questions through provider stream registration with the session workspace, so Ollama provider URL construction and workspace-scoped hooks apply correctly. Fixes #68336. (#70413) Thanks @suboss87.</li>
|
||||
<li>Agents/sessions: make session transcript write locks non-reentrant by default, so same-process transcript writers contend unless a helper explicitly opts into nested lock ownership.</li>
|
||||
<li>ACPX/probe: expose an optional <code>probeAgent</code> plugin config field so the embedded ACP runtime health probe can target a configured agent (for example <code>opencode</code> or <code>claude</code>) instead of hardcoding <code>codex</code>, and stop marking the entire ACP runtime backend unavailable when the default probe agent is simply not installed or not authenticated. (#68409) Thanks @lyfuci.</li>
|
||||
<li>Memory search: use sqlite-vec KNN for vector recall while preserving full post-filter result limits in multi-model indexes. Fixes #69666. (#69680) Thanks @aalekh-sarvam.</li>
|
||||
<li>Providers/OpenAI Codex: stop stale per-agent <code>openai-codex:default</code> OAuth profiles from shadowing a newer main-agent identity-scoped profile, and let <code>openclaw doctor</code> offer the matching cleanup. (#70393) Thanks @pashpashpash.</li>
|
||||
<li>ACPX: route OpenClaw ACP bridge commands through the MCP-free runtime path even when the command is wrapped with <code>env</code>, has bridge flags, or is resumed from persisted session state, so documented <code>acpx openclaw</code> setups no longer fail on per-session MCP injection. (#68741) Thanks @alexlomt.</li>
|
||||
<li>Codex harness: route Codex-tagged MCP tool approval elicitations through OpenClaw plugin approvals, including current empty-schema app-server requests, while leaving generic user-input prompts fail-closed. (#68807) Thanks @kesslerio.</li>
|
||||
<li>WhatsApp/outbound: hold an in-memory active-delivery claim while a live outbound send is in flight, so a concurrent reconnect drain no longer re-drives the same pending queue entry and duplicates cron sends 7-12x after the 30-minute inbound-silence watchdog fires mid-delivery. Crash-replay of fresh queue entries left behind by a dead process is preserved because the claim is intentionally process-local. Fixes #70386. (#70428) Thanks @neeravmakwana.</li>
|
||||
<li>Matrix/commands: keep Matrix DM allowlist state out of room control-command authorization, so trusted DM senders do not accidentally gain room-command access.</li>
|
||||
<li>Providers/SDK retry: cap long <code>Retry-After</code> sleeps in Stainless-based Anthropic/OpenAI model SDKs so 60s+ retry windows surface immediately for OpenClaw failover instead of blocking the run. (#68474) Thanks @jetd1.</li>
|
||||
<li>Agents/TTS: preserve spoken text in TTS tool results while defusing reply directives in transcript content, so future turns remember voice replies without treating spoken <code>MEDIA:</code> or voice tags as delivery metadata. (#68869) Thanks @zqchris.</li>
|
||||
<li>Providers/OpenAI: harden Voice Call realtime transcription against OpenAI Realtime session-update drift, forward language and prompt hints, and add live coverage for realtime STT.</li>
|
||||
<li>Agents/Pi embedded runs: suppress the "⚠️ Agent couldn't generate a response" warning when the assistant already delivered user-visible content through a messaging tool and the turn ended cleanly (<code>stopReason=stop</code>). Real failure modes (tool errors, provider <code>stopReason=error</code>, interrupted tool use) still surface the existing "verify before retrying" warning. Fixes #70396. (#70425) Thanks @neeravmakwana.</li>
|
||||
<li>Gateway/Linux: wrap gateway-managed supervisor, PTY, MCP stdio, and browser child processes in a tiny <code>/bin/sh</code> shim that raises the child's own <code>oom_score_adj</code> on Linux, so under cgroup memory pressure the kernel prefers transient workers over the long-lived gateway. Opt out with <code>OPENCLAW_CHILD_OOM_SCORE_ADJ=0</code>. Fixes #70404. (#70419) Thanks @neeravmakwana.</li>
|
||||
<li>Providers/Moonshot: stop strict-sanitizing Kimi's native tool_call IDs (shaped like <code>functions.<name>:<index></code>) on the OpenAI-compatible transport, so multi-turn agentic flows through Kimi K2.6 no longer break after 2-3 tool-calling rounds when the serving layer fails to match mangled IDs against the original tool definitions. Adds a <code>sanitizeToolCallIds</code> opt-out to the shared <code>openai-compatible</code> replay family helper and wires Moonshot to it. Fixes #62319. (#70030) Thanks @LeoDu0314.</li>
|
||||
<li>Dependencies/security: override transitive <code>uuid</code> to <code>14.0.0</code>, clearing the runtime advisory across dependencies.</li>
|
||||
<li>Codex harness: ignore dynamic tool descriptions when deciding whether to reuse a native app-server thread while still fingerprinting tool schemas, so channel-specific copy changes no longer reset otherwise compatible Codex conversations. (#69976) Thanks @chen-zhang-cs-code.</li>
|
||||
<li>Codex harness: expose the Codex app-server model catalog in <code>models list/status</code>, avoid startup hangs from app-server discovery timeouts, and accept current Codex turn-completion notifications so Docker live gateway turns finish reliably.</li>
|
||||
<li>Codex harness: drop invalid legacy app-server <code>serviceTier</code> values such as <code>"priority"</code> before native thread and turn requests, while keeping supported Codex tiers limited to <code>"fast"</code> and <code>"flex"</code>. Fixes #64815.</li>
|
||||
<li>Codex harness: show bounded, sanitized permission target samples in app-server approval prompts, so native permission requests keep their specific hosts, roots, and paths visible without leaking home usernames or URL credentials. (#70340) Thanks @Lucenx9.</li>
|
||||
<li>Docs/Codex harness: narrow native compaction docs to the current start/completion signals, without promising a readable summary or kept-entry audit list yet. (#69612) Thanks @91wan.</li>
|
||||
<li>Providers/Amazon Bedrock: use known context-window metadata for discovered models while keeping the unknown-model fallback conservative, so compaction and overflow handling improve for newer Bedrock models without overstating unlisted model limits. Thanks @wirjo.</li>
|
||||
<li>Providers/Amazon Bedrock Mantle: refresh IAM-backed bearer tokens at runtime instead of baking discovery-time tokens into provider config, so long-lived Mantle sessions keep working after the initial token ages out. Thanks @wirjo.</li>
|
||||
<li>Config/includes: write through single-file top-level includes for isolated OpenClaw-owned mutations, so <code>plugins install</code> and <code>plugins update</code> update an included <code>plugins.json5</code> file instead of flattening modular <code>$include</code> configs. Fixes #41050 and #66048.</li>
|
||||
<li>Config/reload: plan gateway reloads from source-authored config instead of runtime-materialized snapshots, so plugin update writes no longer trigger false restarts from derived provider/plugin config paths. Fixes #68732.</li>
|
||||
<li>Plugins/update: skip npm plugin reinstall/config rewrites when the installed version and recorded artifact identity already match the registry target, let bare npm package names resolve back to tracked install records, and point already-installed <code>plugins install</code> attempts at <code>plugins update</code> / <code>--force</code> instead of a hook-pack fallback. Fixes #46955, #67957, and #68073.</li>
|
||||
<li>Agents/MCP: keep <code>mcp.servers</code> and bundle MCP tools available in Pi embedded <code>coding</code> and <code>messaging</code> sessions while preserving <code>minimal</code> profile and <code>tools.deny: ["bundle-mcp"]</code> opt-out behavior. Fixes #68875 and #68818.</li>
|
||||
<li>Plugins/startup: tolerate transient bundled-channel catalog/metadata drift while auto-enabling configured plugins, so CLI and gateway startup no longer crash when a channel id is known but its display metadata is unavailable.</li>
|
||||
<li>CLI/Claude: report CLI-backed reply runs as streaming while Claude/Codex CLI turns are still in flight, so WebChat keeps visible response state until the backend finishes. Fixes #70125.</li>
|
||||
<li>Slack/streaming: fall back to normal Slack replies for Slack Connect streams rejected before the SDK flushes its local buffer, so short replies no longer disappear or report success before Slack acknowledges delivery. Fixes #70295. (#70370) Thanks @mvanhorn.</li>
|
||||
<li>Codex harness: rotate the shared app-server websocket client when the configured bearer token changes, so auth-token refreshes reconnect with the new <code>Authorization</code> header instead of reusing a stale socket. (#70328) Thanks @Lucenx9.</li>
|
||||
<li>Channels/sandbox: derive runtime policy keys for external direct messages that share the main conversation, so sandbox/tool policy no longer treats channel-originated DMs as local main-session runs.</li>
|
||||
<li>Config/models: merge provider-scoped model allowlist updates and protect model/provider map writes from accidental full replacement, adding <code>config set --merge</code> for additive updates and <code>--replace</code> for intentional clobbers. Fixes #65920, #68392, and #68653.</li>
|
||||
<li>Agents/Pi auth: preserve AWS SDK-authenticated Bedrock runs for IMDS and task-role setups, clear stale refresh timers on sentinel fallback, and log unexpected runtime-auth prep failures instead of silently leaving the provider unauthenticated. Thanks @wirjo.</li>
|
||||
<li>Config/gateway: restore last-known-good config on critical clobber signatures such as missing metadata, missing <code>gateway.mode</code>, or sharp size drops, preventing gateway crash loops when a valid backup exists. Fixes #70336.</li>
|
||||
<li>Config/gateway: recover configs accidentally prefixed with non-JSON output during gateway startup or <code>openclaw doctor --fix</code>, preserving the clobbered file as a backup while leaving normal config reads read-only.</li>
|
||||
<li>Agents/GitHub Copilot: normalize connection-bound Responses item IDs in the Copilot provider wrapper so replayed histories no longer fail after the upstream connection changes. (#69362) Thanks @Menci.</li>
|
||||
<li>Pi embedded runs: pass real built-in tools into Pi session creation and then narrow active tool names after custom tool registration, so the runner and compaction paths compile cleanly and keep OpenClaw-managed custom tool allowlists without feeding string arrays into <code>createAgentSession</code>. Thanks @vincentkoc.</li>
|
||||
<li>Agents/OpenAI websocket: route native OpenAI websocket metadata and session-header decisions through the shared endpoint classifier so local mocks and custom <code>models.providers.openai.baseUrl</code> endpoints stay out of the native OpenAI path consistently across embedded-runner and websocket transport code. Thanks @vincentkoc.</li>
|
||||
<li>Cron/MCP: retire bundled MCP runtimes through one shared cleanup path for isolated cron run ends, persistent cron session rollover, and direct cron <code>deleteAfterRun</code> fallback cleanup. Fixes #69145, #68623, and #68827.</li>
|
||||
<li>MCP/gateway: tear down stdio MCP process trees on transport close and dispose bundled MCP runtimes during session delete/reset, preventing orphaned wrapper/server processes from accumulating. Fixes #68809 and #69465.</li>
|
||||
<li>Agents/MCP: retire bundled MCP runtimes after completed one-shot subagent cleanup and nested <code>sessions_send</code> steps, while keeping persistent subagent sessions warm.</li>
|
||||
<li>Config: render validation warnings with real line breaks instead of a literal <code>\n</code> sequence in CLI/audit output. Fixes #70140.</li>
|
||||
<li>Cron/doctor: repair malformed persisted cron job IDs through <code>openclaw doctor</code>, including legacy <code>jobId</code>, non-string <code>id</code>, and missing <code>id</code> rows, so <code>cron list</code> no longer needs display-layer coercion for corrupt store data. Fixes #70128.</li>
|
||||
<li>Discord: normalize prefixed channel targets only at the thread-binding API boundary, so <code>sessions_spawn({ runtime: "acp", thread: true })</code> can create child threads from Discord channels without breaking current-channel ACP bindings. (#68034) Thanks @Zetarcos.</li>
|
||||
<li>Discord: harden inbound thread metadata handling against partial Carbon channel getters, so non-command thread messages and queued jobs no longer crash when <code>name</code>, <code>parentId</code>, <code>parent</code>, or <code>ownerId</code> requires fetched raw data.</li>
|
||||
<li>Discord: let <code>message</code> tool reactions resolve <code>user:<id></code> DM targets and preserve <code>channels.discord.guilds.<guild>.channels.<channel>.requireMention: false</code> during reply-stage activation fallback. Fixes #70165 and #69441.</li>
|
||||
<li>Plugins/startup: pre-normalize and cache Jiti alias maps before creating plugin loaders, so module-scoped loader filenames do not reintroduce per-plugin alias-normalization startup cost. Fixes #70186.</li>
|
||||
<li>ACP/Codex: run the bundled Codex ACP harness with an isolated <code>CODEX_HOME</code> and avoid writing incomplete ChatGPT auth bridge files, so Codex ACP sessions no longer clobber the user's real Codex CLI auth. Fixes #70234. Thanks @Lonobers88.</li>
|
||||
<li>Gateway/client: keep long-running RPCs such as ACP <code>agent.wait</code> calls in charge of their own timeout instead of closing the websocket on a missed app-level tick while work is still pending.</li>
|
||||
<li>Telegram/webhooks: lower the grammY webhook callback timeout to 5s so Telegram gets an early 200 response instead of retrying long-running updates as read timeouts. (#70146) Thanks @friday-james.</li>
|
||||
<li>Telegram/polling: rebuild the polling HTTP transport after <code>getUpdates</code> 409 conflicts, so retries use a fresh TCP connection instead of looping on a Telegram-terminated keep-alive socket. (#69873) Thanks @hclsys.</li>
|
||||
<li>Media delivery: strip persisted base64 audio payloads from webchat history, resolve stored <code>media://inbound/*</code> attachments before local-root checks, suppress duplicate Telegram voice/audio sends when TTS emits the same media twice, and support custom image-model IDs that already include their provider prefix.</li>
|
||||
<li>Slack/files: resolve <code>downloadFile</code> bot tokens from the runtime config when callers provide <code>cfg</code> without an explicit token or prebuilt client, preserving cfg-only file downloads outside the action runtime path. (#70160) Thanks @martingarramon.</li>
|
||||
<li>Slack/HTTP: dispatch registered Request URL webhooks through the same handler registry used by Slack monitor setup, so HTTP-mode Slack events no longer 404 after successful route registration. (#70275) Thanks @FroeMic.</li>
|
||||
<li>Slack/runtime bindings: route focused Slack thread replies through their bound ACP session instead of preparing replies against the default agent shell. Fixes #67739. Thanks @Frankla20.</li>
|
||||
<li>CLI/Claude: keep stored Claude CLI sessions through OAuth refresh-token rotation by keying auth epochs on stable account identity instead of mutable OAuth token material. (#70452) Thanks @obviyus.</li>
|
||||
<li>CLI/Claude: verify stored Claude CLI session ids have a readable project transcript before resuming, clearing phantom bindings with <code>reason=transcript-missing</code> instead of silently starting fresh under <code>--resume</code>. Fixes #70177.</li>
|
||||
<li>CLI sessions: persist CLI session clearing through the atomic session-store merge path, so expired Claude/Codex CLI bindings are actually removed before retrying without the stale session id. (#70298) Thanks @HFConsultant.</li>
|
||||
<li>ACP/sessions_spawn: honor explicit <code>model</code> overrides for ACP child sessions instead of silently falling back to the target agent default model. (#70210) Thanks @felix-miao.</li>
|
||||
<li>Diffs/viewer: re-read remote viewer access policy from live runtime config on each request, so toggling <code>plugins.entries.diffs.config.security.allowRemoteViewer</code> closes proxied viewer access immediately instead of waiting for a restart. Thanks @vincentkoc.</li>
|
||||
<li>Diffs/tooling: re-read <code>viewerBaseUrl</code>, presentation defaults, and viewer access policy from live runtime config, and fail closed when the live <code>diffs</code> plugin entry disappears instead of reviving startup viewer settings. Thanks @vincentkoc.</li>
|
||||
<li>Memory/LanceDB: stop resurrecting removed live <code>memory-lancedb</code> hook config from startup snapshots, so deleting or disabling the plugin entry shuts off auto-recall and auto-capture without a restart. Thanks @vincentkoc.</li>
|
||||
<li>Memory/LanceDB: keep auto-recall and auto-capture hooks wired when those settings start disabled, so turning them on in live config starts recall and capture without waiting for a restart. Thanks @vincentkoc.</li>
|
||||
<li>Skill Workshop: keep the tool plus <code>before_prompt_build</code> / <code>agent_end</code> hooks wired while the plugin is disabled at startup, so turning the plugin back on in live config starts guidance and capture without waiting for a restart. Thanks @vincentkoc.</li>
|
||||
<li>Active Memory: stop reviving removed live <code>active-memory</code> config from startup snapshots, so removing the plugin entry turns the hook off immediately instead of waiting for a restart. Thanks @vincentkoc.</li>
|
||||
<li>GitHub Copilot: re-read plugin discovery config from the live runtime snapshot, so toggling <code>plugins.entries.github-copilot.config.discovery.enabled</code> takes effect without a restart. Thanks @vincentkoc.</li>
|
||||
<li>Ollama: re-read plugin discovery config from the live runtime snapshot, so toggling <code>plugins.entries.ollama.config.discovery.enabled</code> takes effect without a restart. Thanks @vincentkoc.</li>
|
||||
<li>OpenAI: re-read the plugin prompt-overlay personality from live runtime config, so GPT-5 system prompt contributions update without a restart when <code>plugins.entries.openai.config.personality</code> changes. Thanks @vincentkoc.</li>
|
||||
<li>Amazon Bedrock: re-read live discovery and guardrail plugin config, so toggling <code>plugins.entries.amazon-bedrock.config.discovery</code> or <code>plugins.entries.amazon-bedrock.config.guardrail</code> takes effect without a restart. Thanks @vincentkoc.</li>
|
||||
<li>Codex: re-read the plugin discovery config from the live runtime snapshot, so toggling <code>plugins.entries.codex.config.discovery</code> takes effect without a restart. Thanks @vincentkoc.</li>
|
||||
<li>Agents/subagents: drop bare <code>NO_REPLY</code> from the parent turn when the session still has pending spawned children, so direct-conversation surfaces such as Telegram DMs no longer rewrite the sentinel into visible fallback chatter while waiting for the child completion event. (#69942) Thanks @neeravmakwana.</li>
|
||||
<li>Plugins/install: keep bundled plugin dependencies off npm install while repairing them when plugins activate from a packaged install, including Feishu/Lark, Browser, and direct bundled channel setup-entry loads.</li>
|
||||
<li>CLI/channels: skip and cache bundled channel plugin, setup, and secrets load failures during read-only discovery, so one broken unused bundled channel cannot crash <code>openclaw status</code> or bootstrap secret scans.</li>
|
||||
<li>Memory/LanceDB: retry initialization after a failed LanceDB load and report unsupported Intel macOS native runtime clearly instead of caching the failure or repeatedly attempting an install that cannot work.</li>
|
||||
<li>CLI/Claude: hash only static extra system prompt parts when deciding whether to reuse a CLI session, so per-message inbound metadata no longer resets Claude CLI conversations on every turn. (#70122) Thanks @zijunl.</li>
|
||||
<li>Hooks/Slack: standardize shared message hook routing fields (<code>threadId</code> / <code>replyToId</code>) and stop Slack outbound delivery from re-running <code>message_sending</code> inside the channel adapter, so plugins like thread-ownership make one outbound routing decision per reply. Thanks @vincentkoc.</li>
|
||||
<li>Auto-reply/media: share one run-scoped reply media context between streamed block delivery and final payload filtering, so a local <code>MEDIA:</code> attachment is staged once and duplicate media sends are suppressed reliably. (#68111) Thanks @ayeshakhalid192007-dev.</li>
|
||||
<li>Plugins/gateway hooks: expose startup config, workspace dir, and a live cron getter on the typed <code>gateway_start</code> hook, and move memory-core managed dreaming off the internal <code>gateway:startup</code> bridge so cron reconciliation stays on the public plugin hook path. Thanks @vincentkoc.</li>
|
||||
<li>Plugins/config: read plugin trust decisions from the source config snapshot when a resolved runtime snapshot is active, so <code>plugins.allow</code> remains enforced and <code>doctor</code>/gateway startup no longer warn that the allowlist is empty when it is configured. Fixes #70161. Also fixes #70141.</li>
|
||||
<li>Agents/openai-completions: enable malformed streamed tool-call argument repair for self-hosted OpenAI-compatible backends such as Kimi/SGLang, so fragmented tool-call arguments no longer reach tools as empty or unusable objects. Fixes #69672. (#70294) Thanks @MonkeyLeeT.</li>
|
||||
<li>Gateway/restart: preserve group and channel chat context when resuming an agent turn after a Gateway restart, so continuation replies keep the same prompt, routing, and tool-status behavior as the original conversation.</li>
|
||||
<li>Gateway/pairing: shared-secret loopback CLI clients now silently auto-approve <code>metadata-upgrade</code> pairing (platform / device family refresh) instead of being disconnected with <code>1008 pairing required</code>. This matches the scope-upgrade and role-upgrade behavior added in #69431 and unblocks non-interactive CLI automation when a paired-device record has a stale platform string (e.g. device key replicated across hosts, install migrated between OSes, or platform-string format changed between OpenClaw versions). Browser / Control-UI clients keep the existing approval-required flow for metadata changes.</li>
|
||||
<li>Gateway/pairing: treat any forwarded-header evidence (<code>Forwarded</code>, <code>X-Forwarded-*</code>, or <code>X-Real-IP</code>) as proxied WebSocket traffic before pairing locality checks, so reverse-proxy topologies cannot use the loopback shared-secret helper auto-pairing path.</li>
|
||||
<li>Agents/OpenAI: treat exact <code>NO_REPLY</code> assistant output as a deliberate silent reply in embedded runs, so GPT-5.4 turns with signed reasoning plus a silent final no longer surface a false incomplete-turn error.</li>
|
||||
<li>Auto-reply/streaming: preserve streamed reply directives through chunk boundaries and phase-aware <code>final_answer</code> delivery, so split <code>MEDIA:<path></code> lines, voice tags, and reply targets reach channel delivery instead of leaking as text or being dropped. (#70243) Thanks @zqchris.</li>
|
||||
<li>Anthropic/Claude Opus 4.7: normalize Opus 4.7 and <code>claude-cli</code> Opus 4.7 variants to a 1M context window in resolved runtime metadata and active-agent status/context reporting, so they no longer inherit the stale 200k fallback. Thanks @BunsDev.</li>
|
||||
<li>Gateway/pairing webchat: render <code>/pair qr</code> replies as structured media instead of raw markdown text, preserve inline reply threading and silent-control handling on media replies, avoid persisting sensitive QR images into transcript history, and keep local webchat media embedding behind internal-only trust markers. (#70047) Thanks @BunsDev.</li>
|
||||
<li>Codex harness: default app-server runs to unchained local execution, so OpenAI heartbeats can use network and shell tools without stalling behind native Codex approvals or the workspace-write sandbox.</li>
|
||||
<li>Codex harness: fail closed for unknown native app-server approval methods instead of routing unsupported future approval shapes through OpenClaw approval grants. (#70356) Thanks @Lucenx9.</li>
|
||||
<li>Codex harness: apply the GPT-5 behavior and heartbeat prompt overlay to native Codex app-server runs, so <code>codex/gpt-5.x</code> sessions get the same follow-through, tool-use, and proactive heartbeat guidance as OpenAI GPT-5 runs.</li>
|
||||
<li>Codex harness: add an explicit Guardian mode for Codex app-server approvals, plus a Docker live probe for approved and ask-back Guardian decisions, while keeping default app-server runs unchained for unattended local heartbeats. The legacy <code>OPENCLAW_CODEX_APP_SERVER_GUARDIAN</code> shortcut is removed; use plugin config <code>appServer.mode: "guardian"</code> or <code>OPENCLAW_CODEX_APP_SERVER_MODE=guardian</code>. Thanks @pashpashpash.</li>
|
||||
<li>OpenAI/Responses: keep embedded OpenAI Responses runs on HTTP when <code>models.providers.openai.baseUrl</code> points at a local mock or other non-public endpoint, so mocked/custom endpoints no longer drift onto the hardcoded public websocket transport. (#69815) Thanks @vincentkoc.</li>
|
||||
<li>Channels/config: require resolved runtime config on channel send/action/client helpers and block runtime helper <code>loadConfig()</code> calls, so SecretRefs are resolved at startup/boundaries instead of being re-read during sends.</li>
|
||||
<li>Discord: pass resolved runtime config through guild and moderation action helpers, so thread-originated Discord commands can run channel, member, role, and guild actions without falling back to runtime config reads. (#70215) Thanks @szponeczek.</li>
|
||||
<li>CLI/channels: preserve bundled setup promotion metadata when a loaded partial channel plugin omits it, so adding a non-default account still moves legacy single-account fields such as Telegram <code>streaming</code> into <code>accounts.default</code>.</li>
|
||||
<li>Telegram: keep the sent-message ownership cache isolated per configured session store, so own-message reaction filtering remains correct with custom <code>session.store</code> paths.</li>
|
||||
<li>Security/update: fail closed when exact pinned npm plugin or hook-pack updates detect integrity drift, and expose aborted plugin drift details in <code>openclaw update --json</code>.</li>
|
||||
<li>Ollama: forward OpenClaw thinking control to native <code>/api/chat</code> requests as top-level <code>think</code>, so <code>/think off</code> and <code>openclaw agent --thinking off</code> suppress thinking on models such as qwen3 instead of idling until the watchdog fires. Fixes #69902. (#69967) Thanks @WZH8898.</li>
|
||||
<li>Memory-core/dreaming: suppress the startup-only managed dreaming cron unavailable warning when the cron service is still attaching, while preserving the runtime warning if cron genuinely remains unavailable. Fixes #69939. (#69941) Thanks @Sanjays2402.</li>
|
||||
<li>Mattermost: suppress reasoning-only payloads even when they arrive as blockquoted <code>> Reasoning:</code> text, preventing <code>/reasoning on</code> from leaking thinking into channel posts. (#69927) Thanks @lawrence3699.</li>
|
||||
<li>Discord: read <code>channel.parentId</code> through a safe accessor in the slash-command, reaction, and model-picker paths so partial <code>GuildThreadChannel</code> prototype getters no longer throw <code>Cannot access rawData on partial Channel</code> when commands like <code>/new</code> run from inside a thread. Fixes #69861. (#69908) Thanks @neeravmakwana.</li>
|
||||
<li>Discord: use safe channel name and parent accessors across voice command authorization, so <code>/vc</code> commands from partial Discord thread channels no longer crash on Carbon rawData getters. (#70199) Thanks @hanamizuki.</li>
|
||||
<li>Discord: make auto-thread parent transcript inheritance opt-in via <code>channels.discord.thread.inheritParent</code>, keeping newly created Discord thread sessions isolated by default while preserving explicit inheritance for configured accounts. Fixes #69907. (#69986) Thanks @Blahdude.</li>
|
||||
<li>Browser/Chrome MCP: reset cached existing-session control sessions when a <code>navigate_page</code> call times out, so one stuck navigation no longer poisons the browser profile until a gateway restart. (#69733) Thanks @ayeshakhalid192007-dev.</li>
|
||||
<li>Browser/Chrome MCP: propagate click timeouts and abort signals to existing-session actions so a stuck click fails fast and reconnects instead of poisoning the browser tool until gateway restart. (#63524) Thanks @dongseok0.</li>
|
||||
<li>Amazon Bedrock/prompt caching: resolve opaque application inference profile targets before injecting Bedrock cache points, require every routed target to support explicit cache points, and retry transient profile lookups instead of caching a false negative for the rest of the process. (#69953) Thanks @anirudhmarc and @vincentkoc.</li>
|
||||
<li>Gateway/channel health: base stale-socket recovery on provider-proven transport activity instead of inbound app-event freshness, preventing quiet Slack, Discord, Telegram, Matrix, and local-style channels from being restarted solely because no user traffic arrived. (#69833) Thanks @bek91.</li>
|
||||
<li>OpenCode Go: canonicalize stale bundled <code>opencode-go</code> base URLs from <code>/go</code> or <code>/go/v1</code> to <code>/zen/go</code> or <code>/zen/go/v1</code>, so older generated model metadata stops hitting the 404 HTML endpoint. (#69898)</li>
|
||||
<li>CLI/channels: honor <code>channels.<id>.enabled=false</code> as a hard read-only presence opt-out, so env vars, manifest env vars, or stale persisted auth state no longer make disabled channel plugins appear in status, doctor, or setup-only discovery.</li>
|
||||
<li>Channels/preview streaming: centralize draft-preview finalization so Slack, Discord, Mattermost, and Matrix no longer flush temporary preview messages for media/error finals, and preserve first-reply threading for normal fallback delivery.</li>
|
||||
<li>Discord: keep slash command follow-up chunks ephemeral when the command is configured for ephemeral replies, so long <code>/status</code> output no longer leaks fallback model or runtime details into the public channel. (#69869) thanks @gumadeiras.</li>
|
||||
<li>Gateway/session history: re-check current auth and <code>chat.history</code> scope before later SSE keepalives and transcript updates, so active session-history streams close before delivering post-revocation events.</li>
|
||||
<li>Plugins/discovery: reject package plugin source entries that escape the package directory before explicit runtime entries or inferred built JavaScript peers can be used. (#69868) thanks @gumadeiras.</li>
|
||||
<li>CLI/channels: resolve channel presence through a shared policy that keeps ambient env vars and stale persisted auth from surfacing disabled bundled plugins in status, doctor, security audit, and cron delivery validation unless the channel or plugin is effectively enabled or explicitly configured. (#69862) Thanks @gumadeiras.</li>
|
||||
<li>Doctor/plugins: hydrate legacy partial interactive handler state before plugin reload clears dedupe caches, so <code>openclaw doctor</code> and post-update doctor runs no longer crash with <code>Cannot read properties of undefined (reading 'clear')</code>. (#70135) Thanks @ngutman.</li>
|
||||
<li>Control UI/config: preserve intentionally empty raw config snapshots when clearing pending updates so reset restores the original bytes instead of synthesizing JSON for blank config files. (#68178) Thanks @BunsDev.</li>
|
||||
<li>memory-core/dreaming: surface a <code>Dreaming status: blocked</code> line in <code>openclaw memory status</code> when dreaming is enabled but the heartbeat that drives the managed cron is not firing for the default agent, and add a Troubleshooting section to the dreaming docs covering the two common causes (per-agent <code>heartbeat</code> blocks excluding <code>main</code>, and <code>heartbeat.every</code> set to <code>0</code>/empty/invalid), so the silent failure described in #69843 becomes legible on the status surface.</li>
|
||||
<li>Cron/run-log: report generic <code>message</code> tool sends under the resolved delivery channel when they match the cron target, while preserving account-specific mismatch checks for delivery traces. (#69940) Thanks @davehappyminion.</li>
|
||||
<li>Doctor/channels: merge configured-channel doctor hooks across read-only, loaded, setup, and runtime plugin discovery so partial adapters no longer hide runtime-only compatibility repair or allowlist warnings, preserve disabled-channel opt-outs, and ignore malformed hook values before they can mask valid fallbacks. (#69919) Thanks @gumadeiras.</li>
|
||||
<li>Models/CLI: show bundled provider-owned static catalog rows in <code>models list --all</code> before auth is configured, including Kimi K2.6 rows for Moonshot, OpenRouter, and Vercel AI Gateway, while keeping local-only and workspace plugin catalog paths isolated. (#69909) Thanks @shakkernerd.</li>
|
||||
<li>Models/CLI: clarify that <code>models list --provider</code> expects provider ids and reject display labels before loading model discovery. (#70504) Thanks @shakkernerd.</li>
|
||||
<li>Configure: skip generic CLI startup bootstrap for <code>openclaw configure</code> and bound hint-only gateway probes so the onboarding TUI reaches its first prompt faster when the Gateway is unavailable. (#69984) Thanks @obviyus.</li>
|
||||
<li>Agents/harness: surface selected plugin harness failures directly instead of replaying the same turn through embedded PI, preventing misleading secondary PI auth errors and avoiding duplicate side effects.</li>
|
||||
<li>OpenAI Codex: add a ChatGPT device-code auth option beside browser OAuth, so headless or callback-hostile setups can sign in without relying on the localhost browser callback. (#69557) Thanks @vincentkoc.</li>
|
||||
<li>CLI sessions: keep provider-owned CLI sessions through implicit daily expiry while preserving explicit reset behavior, and retain Claude CLI binding metadata across gateway agent requests. (#70106) Thanks @obviyus.</li>
|
||||
<li>fix(config): accept truncateAfterCompaction (#68395). Thanks @MonkeyLeeT</li>
|
||||
<li>CLI/Claude: keep Claude CLI session bindings stable across OAuth access-token refreshes, so gateway restarts continue the same Claude conversation instead of minting a fresh one. (#70132) Thanks @obviyus.</li>
|
||||
<li>QQBot: add <code>INTERACTION</code> intent (<code>1 << 26</code>) to the gateway constants and include it in the <code>FULL_INTENTS</code> mask so interaction events are received. (#70143) Thanks @cxyhhhhh.</li>
|
||||
<li>Gateway/restart: preserve one-shot continuation instructions across gateway restarts so agents can resume and reply back to the original chat after reboot. (#63406) Thanks @VACInc.</li>
|
||||
<li>Gateway/restart: write restart sentinel files atomically so interrupted writes cannot leave a truncated sentinel behind. (#70225) Thanks @obviyus.</li>
|
||||
<li>Pairing: remove stale pending requests for a device when that paired device is deleted, so an old repair approval cannot recreate the removed device from leftover state.</li>
|
||||
<li>Security/dotenv: block workspace <code>.env</code> overrides for Matrix, Mattermost, IRC, and Synology endpoint settings so cloned workspaces cannot redirect bundled connector traffic through local endpoint config. (#70240) Thanks @drobison00.</li>
|
||||
<li>Telegram: require the same <code>/models</code> authorization for group model-picker callbacks, so unauthorized participants can no longer browse or change the session model through inline buttons. (#70235) Thanks @drobison00.</li>
|
||||
<li>Agents/Pi: keep the filtered tool-name allowlist active for embedded OpenAI/OpenAI Codex GPT-5 runs and compaction sessions, so bundled and client tools still execute after the Pi <code>0.68.1</code> session-tool allowlist change instead of stopping at plan-only replies with no tool call. (#70281) Thanks @jalehman.</li>
|
||||
<li>Agents/Pi: honor explicit <code>strict-agentic</code> execution contracts for incomplete-turn retry guards across providers, so manually opted-in local or compatible models get the same retry behavior without relying on OpenAI model inference. (#66750) Thanks @ziomancer.</li>
|
||||
<li>OpenShell/sandbox: pin verified file reads to an already-opened descriptor, walk the ancestor chain for symlinked parents on platforms without fd-path readlink, and re-check file identity so parent symlink swaps cannot redirect in-sandbox reads to host files outside the allowed mount root. (#69798) Thanks @drobison00.</li>
|
||||
<li>Gateway/Control UI: require authenticated Control UI read access before serving <code>/__openclaw/control-ui-config.json</code> when <code>gateway.auth</code> is enabled, so unauthenticated callers can no longer read bootstrap metadata. (#70247) Thanks @drobison00.</li>
|
||||
<li>Gateway/restart: default session-scoped restart sentinels to a one-shot agent continuation, so chat-initiated Gateway restarts acknowledge successful boot automatically. (#70269) Thanks @obviyus.</li>
|
||||
<li>Build/npm publish: fail postpublish verification when root <code>dist/*</code> files import bundled plugin runtime dependencies without mirroring them in the root package manifest, so Slack-style plugin deps cannot silently ship on the wrong module-resolution path again. (#60112) thanks @medns.</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.4.22/OpenClaw-2026.4.22.zip" length="47883836" type="application/octet-stream" sparkle:edSignature="kzJ2j2sWX4H+ZIc4dXEFORYr9tk3w1txpjCJ38cdSFz6yWHU0M6Sx9zN0DB7JGIpv1QC+D+jFbWBkl4SJqW2AA=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.4.20</title>
|
||||
<pubDate>Tue, 21 Apr 2026 19:53:52 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026042090</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.4.20</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.4.20</h2>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Onboard/wizard: restyle the setup security disclaimer with a single yellow warning banner, section headings and bulleted checklists, and un-dim the note body so key guidance is easy to scan; add a loading spinner during the initial model catalog load so the wizard no longer goes blank while it runs; add an "API key" placeholder to provider API key prompts. (#69553) Thanks @Patrick-Erichsen.</li>
|
||||
<li>Agents/prompts: strengthen the default system prompt and OpenAI GPT-5 overlay with clearer completion bias, live-state checks, weak-result recovery, and verification-before-final guidance.</li>
|
||||
<li>Models/costs: support tiered model pricing from cached catalogs and configured models, and include bundled Moonshot Kimi K2.6/K2.5 cost estimates for token-usage reports. (#67605) Thanks @sliverp.</li>
|
||||
<li>Sessions/Maintenance: enforce the built-in entry cap and age prune by default, and prune oversized stores at load time so accumulated cron/executor session backlogs cannot OOM the gateway before the write path runs. (#69404) Thanks @bobrenze-bot.</li>
|
||||
<li>Plugins/tests: reuse plugin loader alias and Jiti config resolution across repeated same-context loads, reducing import-heavy test overhead. (#69316) Thanks @amknight.</li>
|
||||
<li>Cron: split runtime execution state into <code>jobs-state.json</code> so <code>jobs.json</code> stays stable for git-tracked job definitions. (#63105) Thanks @Feelw00.</li>
|
||||
<li>Agents/compaction: send opt-in start and completion notices during context compaction. (#67830) Thanks @feniix.</li>
|
||||
<li>Moonshot/Kimi: default bundled Moonshot setup, web search, and media-understanding surfaces to <code>kimi-k2.6</code> while keeping <code>kimi-k2.5</code> available for compatibility. (#69477) Thanks @scoootscooob.</li>
|
||||
<li>Moonshot/Kimi: allow <code>thinking.keep = "all"</code> on <code>moonshot/kimi-k2.6</code>, and strip it for other Moonshot models or requests where pinned <code>tool_choice</code> disables thinking. (#68816) Thanks @aniaan.</li>
|
||||
<li>BlueBubbles/groups: forward per-group <code>systemPrompt</code> config into inbound context <code>GroupSystemPrompt</code> so configured group-specific behavioral instructions (for example threaded-reply and tapback conventions) are injected on every turn. Supports <code>"*"</code> wildcard fallback matching the existing <code>requireMention</code> pattern. Closes #60665. (#69198) Thanks @omarshahine.</li>
|
||||
<li>Plugins/tasks: add a detached runtime registration contract so plugin executors can own detached task lifecycle and cancellation without reaching into core task internals. (#68915) Thanks @mbelinky.</li>
|
||||
<li>Terminal/logging: optimize <code>sanitizeForLog()</code> by replacing the iterative control-character stripping loop with a single regex pass while preserving the existing ANSI-first sanitization behavior. (#67205) Thanks @bulutmuf.</li>
|
||||
<li>QA/CI: make <code>openclaw qa suite</code> and <code>openclaw qa telegram</code> fail by default when scenarios fail, add <code>--allow-failures</code> for artifact-only runs, and tighten live-lane defaults for CI automation. (#69122) Thanks @joshavant.</li>
|
||||
<li>Mattermost: stream thinking, tool activity, and partial reply text into a single draft preview post that finalizes in place when safe. (#47838) thanks @ninjaa.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Exec/YOLO: stop rejecting gateway-host exec in <code>security=full</code> plus <code>ask=off</code> mode via the Python/Node script preflight hardening path, so promptless YOLO exec once again runs direct interpreter stdin and heredoc forms such as <code>node <<'NODE' ... NODE</code>.</li>
|
||||
<li>OpenAI Codex: normalize legacy <code>openai-completions</code> transport overrides on default OpenAI/Codex and GitHub Copilot-compatible hosts back to the native Codex Responses transport while leaving custom proxies untouched. (#45304, #42194) Thanks @dyss1992 and @DeadlySilent.</li>
|
||||
<li>Anthropic/plugins: scope Anthropic <code>api: "anthropic-messages"</code> defaulting to Anthropic-owned providers, so <code>openai-codex</code> and other providers without an explicit <code>api</code> no longer get rewritten to the wrong transport. Fixes #64534.</li>
|
||||
<li>fix(qqbot): add SSRF guard to direct-upload URL paths in uploadC2CMedia and uploadGroupMedia [AI-assisted]. (#69595) Thanks @pgondhi987.</li>
|
||||
<li>fix(gateway): enforce allowRequestSessionKey gate on template-rendered mapping sessionKeys. (#69381) Thanks @pgondhi987.</li>
|
||||
<li>Browser/Chrome MCP: surface <code>DevToolsActivePort</code> attach failures as browser-connectivity errors instead of a generic "waiting for tabs" timeout, and point signed-out fallbacks toward the managed <code>openclaw</code> profile.</li>
|
||||
<li>Webchat/images: treat inline image attachments as media for empty-turn gating while still ignoring metadata-only blank turns. (#69474) Thanks @Jaswir.</li>
|
||||
<li>Discord/think: only show <code>adaptive</code> in <code>/think</code> autocomplete for provider/model pairs that actually support provider-managed adaptive thinking, so GPT/OpenAI models no longer advertise an Anthropic-only option.</li>
|
||||
<li>Thinking: only expose <code>max</code> for models that explicitly support provider max reasoning, and remap stored <code>max</code> settings to the largest supported thinking mode when users switch to another model.</li>
|
||||
<li>Gateway/usage: bound the cost usage cache with FIFO eviction so date/range lookups cannot grow unbounded. (#68842) Thanks @Feelw00.</li>
|
||||
<li>OpenAI/Responses: resolve <code>/think</code> levels against each GPT model's supported reasoning efforts so <code>/think off</code> no longer becomes high reasoning or sends unsupported <code>reasoning.effort: "none"</code> payloads.</li>
|
||||
<li>Lobster/TaskFlow: allow managed approval resumes to use <code>approvalId</code> without a resume token, and persist that id in approval wait state. (#69559) Thanks @kirkluokun.</li>
|
||||
<li>Plugins/startup: install bundled runtime dependencies into each plugin's own runtime directory, reuse source-checkout repair caches after rebuilds, and log only packages that were actually installed so repeated Gateway starts stay quiet once deps are present.</li>
|
||||
<li>Plugins/startup: ignore pnpm's <code>npm_execpath</code> when repairing bundled plugin runtime dependencies and skip workspace-only package specs so npm-only install flags or local workspace links do not break packaged plugin startup.</li>
|
||||
<li>MCP: block interpreter-startup env keys such as <code>NODE_OPTIONS</code> for stdio servers while preserving ordinary credential and proxy env vars. (#69540) Thanks @drobison00.</li>
|
||||
<li>Agents/shell: ignore non-interactive placeholder shells like <code>/usr/bin/false</code> and <code>/sbin/nologin</code>, falling back to <code>sh</code> so service-user exec runs no longer exit immediately. (#69308) Thanks @sk7n4k3d.</li>
|
||||
<li>Setup/TUI: relaunch the setup hatch TUI in a fresh process while preserving the configured gateway target and auth source, so onboarding recovers terminal state cleanly without exposing gateway secrets on command-line args. (#69524) Thanks @shakkernerd.</li>
|
||||
<li>Codex: avoid re-exposing the image-generation tool on native vision turns with inbound images, and keep bare image-model overrides on the configured image provider. (#65061) Thanks @zhulijin1991.</li>
|
||||
<li>Sessions/reset: clear auto-sourced model, provider, and auth-profile overrides on <code>/new</code> and <code>/reset</code> while preserving explicit user selections, so channel sessions stop staying pinned to runtime fallback choices. (#69419) Thanks @sk7n4k3d.</li>
|
||||
<li>Sessions/costs: snapshot <code>estimatedCostUsd</code> like token counters so repeated persist paths no longer compound the same run cost by up to dozens of times. (#69403) Thanks @MrMiaigi.</li>
|
||||
<li>OpenAI Codex: route ChatGPT/Codex OAuth Responses requests through the <code>/backend-api/codex</code> endpoint so <code>openai-codex/gpt-5.4</code> no longer hits the removed <code>/backend-api/responses</code> alias. (#69336) Thanks @mzogithub.</li>
|
||||
<li>OpenAI/Responses: omit disabled reasoning payloads when <code>/think off</code> is active, so GPT reasoning models no longer receive unsupported <code>reasoning.effort: "none"</code> requests. (#61982) Thanks @a-tokyo.</li>
|
||||
<li>Gateway/pairing: treat loopback shared-secret node-host, TUI, and gateway clients as local for pairing decisions, so trusted local tools no longer reconnect as remote clients and fail with <code>pairing required</code>. (#69431) Thanks @SARAMALI15792.</li>
|
||||
<li>Active Memory: degrade gracefully when memory recall fails during prompt building, logging a warning and letting the reply continue without memory context instead of failing the whole turn. (#69485) Thanks @Magicray1217.</li>
|
||||
<li>Ollama: add provider-policy defaults for <code>baseUrl</code> and <code>models</code> so implicit local discovery can run before config validation rejects a minimal Ollama provider config. (#69370) Thanks @PratikRai0101.</li>
|
||||
<li>Agents/model selection: clear transient auto-failover session overrides before each turn so recovered primary models are retried immediately without emitting user-override reset warnings. (#69365) Thanks @hitesh-github99.</li>
|
||||
<li>Auto-reply: apply silent <code>NO_REPLY</code> policy per conversation type, so direct chats get a helpful rewritten reply while groups and internal deliveries can remain quiet. (#68644) Thanks @Takhoffman.</li>
|
||||
<li>Telegram/status reactions: honor <code>messages.removeAckAfterReply</code> when lifecycle status reactions are enabled, clearing or restoring the reaction after success/error using the configured hold timings. (#68067) Thanks @poiskgit.</li>
|
||||
<li>Web search/plugins: resolve plugin-scoped SecretRef API keys for bundled Exa, Firecrawl, Gemini, Kimi, Perplexity, Tavily, and Grok web-search providers when they are selected through the shared web-search config. (#68424) Thanks @afurm.</li>
|
||||
<li>Telegram/polling: raise the default polling watchdog threshold from 90s to 120s and add configurable <code>channels.telegram.pollingStallThresholdMs</code> (also per-account) so long-running Telegram work gets more room before polling is treated as stalled. (#57737) Thanks @Vitalcheffe.</li>
|
||||
<li>Telegram/polling: bound the persisted-offset confirmation <code>getUpdates</code> probe with a client-side timeout so a zombie socket cannot hang polling recovery before the runner watchdog starts. (#50368) Thanks @boticlaw.</li>
|
||||
<li>Agents/Pi runner: retry silent <code>stopReason=error</code> turns with no output when no side effects ran, so non-frontier providers that briefly return empty error turns get another chance instead of ending the session early. (#68310) Thanks @Chased1k.</li>
|
||||
<li>Plugins/memory: preserve the active memory capability when read-only snapshot plugin loads run, so status and provider discovery paths no longer wipe memory public artifacts. (#69219) Thanks @zeroaltitude.</li>
|
||||
<li>Plugins: keep only the highest-precedence manifest when distinct discovered plugins share an id, so lower-precedence global or workspace duplicates no longer load beside bundled or config-selected plugins. (#41626) Thanks @Tortes.</li>
|
||||
<li>fix(security): block MINIMAX_API_HOST workspace env injection and remove env-driven URL routing [AI-assisted]. (#67300) Thanks @pgondhi987.</li>
|
||||
<li>Cron/delivery: treat explicit <code>delivery.mode: "none"</code> runs as not requested even if the runner reports <code>delivered: false</code>, so no-delivery cron jobs no longer persist false delivery failures or errors. (#69285) Thanks @matsuri1987.</li>
|
||||
<li>Plugins/install: repair active and default-enabled bundled plugin runtime dependencies before import in packaged installs, so bundled Discord, WhatsApp, Slack, Telegram, and provider plugins work without putting their dependency trees in core.</li>
|
||||
<li>BlueBubbles: raise the outbound <code>/api/v1/message/text</code> send timeout default from 10s to 30s, and add a configurable <code>channels.bluebubbles.sendTimeoutMs</code> (also per-account) so macOS 26 setups where Private API iMessage sends stall for 60+ seconds no longer silently lose messages at the 10s abort. Probes, chat lookups, and health checks keep the shorter 10s default. Fixes #67486. (#69193) Thanks @omarshahine.</li>
|
||||
<li>Agents/bootstrap: budget truncation markers against per-file caps, preserve source content instead of silently wasting bootstrap bytes, and avoid marker-only output in tiny-budget truncation cases. (#69114) Thanks @BKF-Gitty.</li>
|
||||
<li>Context engine/plugins: stop rejecting third-party context engines whose <code>info.id</code> differs from the registered plugin slot id. The strict-match contract added in 2026.4.14 broke <code>lossless-claw</code> and other plugins whose internal engine id does not equal the slot id they are registered under, producing repeated <code>info.id must match registered id</code> lane failures on every turn. Fixes #66601. (#66678) Thanks @GodsBoy.</li>
|
||||
<li>Agents/compaction: rename embedded Pi compaction lifecycle events to <code>compaction_start</code> / <code>compaction_end</code> so OpenClaw stays aligned with <code>pi-coding-agent</code> 0.66.1 event naming. (#67713) Thanks @mpz4life.</li>
|
||||
<li>Security/dotenv: block all <code>OPENCLAW_*</code> keys from untrusted workspace <code>.env</code> files so workspace-local env loading fails closed for new runtime-control variables instead of silently inheriting them. (#473)</li>
|
||||
<li>Gateway/device pairing: restrict non-admin paired-device sessions (device-token auth) to their own pairing list, approve, and reject actions so a paired device cannot enumerate other devices or approve/reject pairing requests authored by another device. Admin and shared-secret operator sessions retain full visibility. (#69375) Thanks @eleqtrizit.</li>
|
||||
<li>Agents/gateway tool: extend the agent-facing <code>gateway</code> tool's config mutation guard so model-driven <code>config.patch</code> and <code>config.apply</code> cannot rewrite operator-trusted paths (sandbox, plugin trust, gateway auth/TLS, hook routing and tokens, SSRF policy, MCP servers, workspace filesystem hardening) and cannot bypass the guard by editing per-agent sandbox, tools, or embedded-Pi overrides in place under <code>agents.list[]</code>. (#69377) Thanks @eleqtrizit.</li>
|
||||
<li>Gateway/websocket broadcasts: require <code>operator.read</code> (or higher) for chat, agent, and tool-result event frames so pairing-scoped and node-role sessions no longer passively receive session chat content, and scope-gate unknown broadcast events by default. Plugin-defined <code>plugin.*</code> broadcasts are scoped to operator.write/admin, and status/transport events (<code>heartbeat</code>, <code>presence</code>, <code>tick</code>, etc.) remain unrestricted. Per-client sequence numbers preserve per-connection monotonicity. (#69373) Thanks @eleqtrizit.</li>
|
||||
<li>Agents/compaction: always reload embedded Pi resources through an explicit loader and reapply reserve-token overrides so runs without extension factories no longer silently lose compaction settings before session start. (#67146) Thanks @ly85206559.</li>
|
||||
<li>Memory-core/dreaming: normalize sweep timestamps and reuse hashed narrative session keys for fallback cleanup so Dreaming narrative sub-sessions stop leaking. (#67023) Thanks @chiyouYCH.</li>
|
||||
<li>Gateway/startup: delay HTTP bind until websocket handlers are attached, so immediate post-startup websocket health/connect probes no longer hit the startup race window. (#43392) Thanks @dalefrieswthat.</li>
|
||||
<li>Codex/app-server: release the session lane when a downstream consumer throws while draining the <code>turn/completed</code> notification, so follow-up messages after a Codex plugin reply stop queueing behind a stale lane lock. Fixes #67996. (#69072) Thanks @ayeshakhalid192007-dev.</li>
|
||||
<li>Codex/app-server: default approval handling to <code>on-request</code> so Codex harness sessions do not start with overly permissive tool approvals. (#68721) Thanks @Lucenx9.</li>
|
||||
<li>Cron/delivery: keep isolated cron chat delivery tools available, resolve <code>channel: "last"</code> targets from the gateway, show delivery previews in <code>cron list/show</code>, and avoid duplicate fallback sends after direct message-tool delivery. (#69587) Thanks @obviyus.</li>
|
||||
<li>Cron/Telegram: key isolated direct-delivery dedupe to each cron execution instead of the reused session id, so recurring Telegram announce runs no longer report delivered while silently skipping later sends. (#69000) Thanks @obviyus.</li>
|
||||
<li>Models/Kimi: default bundled Kimi thinking to off and normalize Anthropic-compatible <code>thinking</code> payloads so stale session <code>/think</code> state no longer silently re-enables reasoning on Kimi runs. (#68907) Thanks @frankekn.</li>
|
||||
<li>Control UI/cron: keep the runtime-only <code>last</code> delivery sentinel from being materialized into persisted cron delivery and failure-alert channel configs when jobs are created or edited. (#68829) Thanks @tianhaocui.</li>
|
||||
<li>OpenAI/Responses: strip orphaned reasoning blocks before outbound Responses API calls so compacted or restored histories no longer fail on standalone reasoning items. (#55787) Thanks @suboss87.</li>
|
||||
<li>Cron/CLI: parse PowerShell-style <code>--tools</code> allow-lists the same way as comma-separated input, so <code>cron add</code> and <code>cron edit</code> no longer persist <code>exec read write</code> as one combined tool entry on Windows. (#68858) Thanks @chen-zhang-cs-code.</li>
|
||||
<li>Browser/user-profile: let existing-session <code>profile="user"</code> tool calls auto-route to a connected browser node or use explicit <code>target="node"</code>, while still honoring explicit <code>target="host"</code> pinning. (#48677)</li>
|
||||
<li>Discord/slash commands: tolerate partial Discord channel metadata in slash-command and model-picker flows so partial channel objects no longer crash when channel names, topics, or thread parent metadata are unavailable. (#68953) Thanks @dutifulbob.</li>
|
||||
<li>BlueBubbles: consolidate outbound HTTP through a typed <code>BlueBubblesClient</code> that resolves the SSRF policy once at construction so image attachments stop getting blocked on localhost and reactions stop getting blocked on private-IP BB deployments. Fixes #34749 and #59722. (#68234) Thanks @omarshahine.</li>
|
||||
<li>Cron/gateway: reject ambiguous announce delivery config at add/update time so invalid multi-channel or target-id provider settings fail early instead of persisting broken cron jobs. (#69015) Thanks @obviyus.</li>
|
||||
<li>Cron/main-session delivery: preserve <code>heartbeat.target="last"</code> through deferred wake queuing, gateway wake forwarding, and same-target wake coalescing so queued cron replies still return to the last active chat. (#69021) Thanks @obviyus.</li>
|
||||
<li>Cron/gateway: ignore disabled channels when announce delivery ambiguity is checked, and validate main-session delivery patches against the live cron service default agent so hot-reloaded agent config does not falsely reject valid updates. (#69040) Thanks @obviyus.</li>
|
||||
<li>Matrix/allowlists: hot-reload <code>dm.allowFrom</code> and <code>groupAllowFrom</code> entries on inbound messages while keeping config removals authoritative, so Matrix allowlist changes no longer require a channel restart to add or revoke a sender. (#68546) Thanks @johnlanni.</li>
|
||||
<li>BlueBubbles: always set <code>method</code> explicitly on outbound text sends (<code>"private-api"</code> when available, <code>"apple-script"</code> otherwise), and prefer Private API on macOS 26 even for plain text. Fixes silent delivery failure on macOS setups without Private API where an omitted <code>method</code> let BB Server fall back to version-dependent default behavior that silently drops the message (#64480), and the AppleScript <code>-1700</code> error on macOS 26 Tahoe plain text sends (#53159). (#69070) Thanks @xqing3.</li>
|
||||
<li>Matrix/commands: recognize slash commands that are prefixed with the bot's Matrix mention, so room messages like <code>@bot:server /new</code> trigger the command path without requiring custom mention regexes. (#68570) Thanks @nightq and @johnlanni.</li>
|
||||
<li>Gateway/pairing: return reason-specific <code>PAIRING_REQUIRED</code> details, remediation hints, and request ids so unapproved-device and scope-upgrade failures surface actionable recovery guidance in the CLI and Control UI. (#69227) Thanks @obviyus.</li>
|
||||
<li>Agents/subagents: include requested role and runtime timing on subagent failure payloads so parent agents can correlate failed or timed-out child work. (#68726) Thanks @BKF-Gitty.</li>
|
||||
<li>Gateway/sessions: reject stale agent-scoped sessions after an agent is removed from config while preserving legacy default-agent main-session aliases. (#65986) Thanks @bittoby.</li>
|
||||
<li>Doctor/gateway: surface pending device pairing requests, scope-upgrade approval drift, and stale device-token mismatch repair steps so <code>openclaw doctor --fix</code> no longer leaves pairing/auth setup failures unexplained. (#69210) Thanks @obviyus.</li>
|
||||
<li>Cron/isolated-agent: preserve explicit <code>delivery.mode: "none"</code> message targets for isolated runs without inheriting implicit <code>last</code> routing, so agent-initiated Telegram sends keep their authored destination while bare <code>mode:none</code> jobs stay targetless. (#69153) Thanks @obviyus.</li>
|
||||
<li>Cron/isolated-agent: keep <code>delivery.mode: "none"</code> account-only or thread-only configs from inheriting a stale implicit recipient, so isolated runs only resolve message routing when the job authored an explicit <code>to</code> target. (#69163) Thanks @obviyus.</li>
|
||||
<li>Gateway/TUI: retry session history while the local gateway is still finishing startup, so <code>openclaw tui</code> reconnects no longer fail on transient <code>chat.history unavailable during gateway startup</code> errors. (#69164) Thanks @shakkernerd.</li>
|
||||
<li>BlueBubbles/reactions: fall back to <code>love</code> when an agent reacts with an emoji outside the iMessage tapback set (<code>love</code>/<code>like</code>/<code>dislike</code>/<code>laugh</code>/<code>emphasize</code>/<code>question</code>), so wider-vocabulary model reactions like <code>👀</code> still produce a visible tapback instead of failing the whole reaction request. Configured ack reactions still validate strictly via the new <code>normalizeBlueBubblesReactionInputStrict</code> path. (#64693) Thanks @zqchris.</li>
|
||||
<li>BlueBubbles: prefer iMessage over SMS when both chats exist for the same handle, honor explicit <code>sms:</code> targets, and never silently downgrade iMessage-available recipients. (#61781) Thanks @rmartin.</li>
|
||||
<li>Telegram/setup: require numeric <code>allowFrom</code> user IDs during setup instead of offering unsupported <code>@username</code> DM resolution, and point operators to <code>from.id</code>/<code>getUpdates</code> for discovery. (#69191) Thanks @obviyus.</li>
|
||||
<li>GitHub Copilot/onboarding: default GitHub Copilot setup to <code>claude-opus-4.6</code> and keep the bundled default model list aligned, so new Copilot setups no longer start on the older <code>gpt-4o</code> default. (#69207) Thanks @obviyus.</li>
|
||||
<li>Gateway/status: separate reachability, capability, and read-probe reporting so connect-only or scope-limited sessions no longer look fully healthy, and normalize SSH targets entered as <code>ssh user@host</code>. (#69215) Thanks @obviyus.</li>
|
||||
<li>Slack: fix outbound replies failing with "unresolved SecretRef" for accounts configured via <code>file</code> or <code>exec</code> secret sources; the send path now tolerates the runtime snapshot retaining an unresolved channel SecretRef when a boot-resolved token override is already available. (#68954) Thanks @openperf.</li>
|
||||
<li>Control UI/device pairing: explain scope and role approval upgrades during reconnects, and show requested versus approved access in the Control UI and <code>openclaw devices</code> so broader reconnects no longer look like lost pairings. (#69221) Thanks @obviyus.</li>
|
||||
<li>Gateway/Control UI: surface pending scope, role, and device-metadata pairing approvals in auth errors and Control UI hints so broader reconnects no longer look like random auth breakage. (#69226) Thanks @obviyus.</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.4.20/OpenClaw-2026.4.20.zip" length="47535600" type="application/octet-stream" sparkle:edSignature="D7XcNGxmc10IIayYY91RZBoascFSnXyd4dg6cSpC3+PTIwVrWYs/FwSBc/1J+1P53LlnTHKDGQYMkWVNMnRSAQ=="/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -1,16 +0,0 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{kt,kts}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = off
|
||||
ktlint_standard_filename = disabled
|
||||
ktlint_standard_function-naming = disabled
|
||||
ktlint_standard_max-line-length = disabled
|
||||
ktlint_standard_property-naming = disabled
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user