mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 14:01:24 +08:00
Compare commits
5 Commits
fix/matter
...
codex/remo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae03b79b73 | ||
|
|
d507d5a766 | ||
|
|
a7a3a5f37b | ||
|
|
2476992301 | ||
|
|
1151e7d40b |
@@ -1,6 +1,11 @@
|
||||
---
|
||||
name: blacksmith-testbox
|
||||
description: Run Blacksmith Testbox for CI-parity checks, secrets, hosted services, migrations, or builds local cannot reproduce.
|
||||
description: >
|
||||
Validate code changes against real CI when local execution is not
|
||||
enough. Use for CI-parity checks, secrets/services, migrations, or
|
||||
builds/tests that cannot run reliably on the local machine. Do not
|
||||
replace repo-documented local test/build loops just because this
|
||||
skill exists.
|
||||
---
|
||||
|
||||
# Blacksmith Testbox
|
||||
@@ -16,19 +21,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 +86,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 +94,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 +147,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 +231,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 +247,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 +273,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 +329,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,339 +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, required user-facing OpenClaw changelog entries are
|
||||
present, 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`.
|
||||
|
||||
When Peter asks Codex to create a PR and enable ClawSweeper automerge, do not
|
||||
leave his local OpenClaw checkout on the PR branch. After the PR is created,
|
||||
pushed, and the `@clawsweeper automerge` request is posted or otherwise
|
||||
confirmed, return the local checkout to `main` and fast-forward it when the
|
||||
working tree is clean:
|
||||
|
||||
```bash
|
||||
git switch main
|
||||
git pull --ff-only
|
||||
```
|
||||
|
||||
If unrelated local edits or an in-progress rebase prevent switching, report the
|
||||
blocker instead of stashing, deleting, or overwriting work.
|
||||
|
||||
Repair caps:
|
||||
|
||||
```bash
|
||||
CLAWSWEEPER_MAX_REPAIRS_PER_PR=10
|
||||
CLAWSWEEPER_MAX_REPAIRS_PER_HEAD=1
|
||||
```
|
||||
|
||||
## Security Boundary
|
||||
|
||||
Do not stage unapproved 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 PRs explicitly opted into `clawsweeper:autofix` or
|
||||
`clawsweeper:automerge`, security-sensitive review findings may dispatch
|
||||
bounded repair, but merge remains blocked until a later exact-head review is
|
||||
clean and the normal merge gates pass. 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."
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-ghsa-maintainer
|
||||
description: Inspect, patch, validate, publish, or confirm OpenClaw GHSA security advisories and private-fork state.
|
||||
description: Maintainer workflow for OpenClaw GitHub Security Advisories (GHSA). Use when Codex needs to inspect, patch, validate, or publish a repo advisory, verify private-fork state, prepare advisory Markdown or JSON payloads safely, handle GHSA API-specific publish constraints, or confirm advisory publish success.
|
||||
---
|
||||
|
||||
# OpenClaw GHSA Maintainer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-parallels-smoke
|
||||
description: Run, rerun, debug, or interpret OpenClaw Parallels install, onboarding, gateway smoke, and upgrade checks.
|
||||
description: End-to-end Parallels smoke, upgrade, and rerun workflow for OpenClaw across macOS, Windows, and Linux guests. Use when Codex needs to run, rerun, debug, or interpret VM-based install, onboarding, gateway smoke tests, latest-release-to-main upgrade checks, fresh snapshot retests, or optional Discord roundtrip verification under Parallels.
|
||||
---
|
||||
|
||||
# OpenClaw Parallels Smoke
|
||||
@@ -45,9 +45,6 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
|
||||
## npm install then update
|
||||
|
||||
- Preferred entrypoint: `pnpm test:parallels:npm-update`
|
||||
- For a macOS-only published release update check, use:
|
||||
- `timeout --foreground 75m pnpm test:parallels:npm-update -- --platform macos --package-spec openclaw@<old-version> --update-target <target-version-or-tag> --json`
|
||||
This keeps the same-guest `openclaw update --tag ...` coverage and uses the shared macOS current-user/sudo fallback without starting Windows/Linux lanes.
|
||||
- Required coverage: every release/update regression run must include both lanes:
|
||||
- fresh snapshot -> install requested package/baseline -> smoke
|
||||
- same guest baseline -> run the guest's installed `openclaw update ...` command -> smoke again
|
||||
@@ -78,7 +75,6 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
|
||||
## macOS flow
|
||||
|
||||
- Preferred entrypoint: `pnpm test:parallels:macos`
|
||||
- `parallels-macos-smoke.sh --mode fresh --target-package-spec openclaw@<version>` is an install smoke only. For published old-version -> new-version update coverage on macOS, prefer the npm-update wrapper with `--platform macos`; `parallels-macos-smoke.sh --mode upgrade --target-package-spec ...` installs the target package and does not exercise the baseline CLI's updater.
|
||||
- Default upgrade coverage on macOS should now include: fresh snapshot -> site installer pinned to the latest stable tag -> `openclaw update --channel dev` on the guest. Treat this as part of the default Tahoe regression plan, not an optional side quest.
|
||||
- `parallels-macos-smoke.sh --mode upgrade` should run that release-to-dev lane by default. Keep the older host-tgz upgrade path only when the caller explicitly passes `--target-package-spec`.
|
||||
- Because the default upgrade lane no longer needs a host tgz, skip `npm pack` + host HTTP server startup for `--mode upgrade` unless `--target-package-spec` is set. Keep the pack/server path for `fresh` and `both`.
|
||||
@@ -148,7 +144,6 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
|
||||
- `--discord-token-env`
|
||||
- `--discord-guild-id`
|
||||
- `--discord-channel-id`
|
||||
- After a successful Discord smoke/roundtrip, shut down the guest VM before handoff (`prlctl stop "$VM_NAME"` or the concrete VM name). The macOS smoke harness should do this automatically after successful Discord proof; still stop the VM manually after ad-hoc Discord checks. Do not leave the Discord-configured guest running; it can keep reading/posting in `#maintainer` and spam Discord after the proof is complete.
|
||||
- Keep the Discord token only in a host env var.
|
||||
- Use installed `openclaw message send/read`, not `node openclaw.mjs message ...`.
|
||||
- Set `channels.discord.guilds` as one JSON object, not dotted config paths with snowflakes.
|
||||
|
||||
@@ -1,29 +1,12 @@
|
||||
---
|
||||
name: openclaw-pr-maintainer
|
||||
description: Review, triage, close, label, comment on, or land OpenClaw PRs/issues with maintainer evidence checks.
|
||||
description: Maintainer workflow for reviewing, triaging, preparing, closing, or landing OpenClaw pull requests and related issues. Use when Codex needs to validate bug-fix claims, search for related issues or PRs, apply or recommend close/reason labels, prepare GitHub comments safely, check review-thread follow-up, or perform maintainer-style PR decision making before merge or closure.
|
||||
---
|
||||
|
||||
# OpenClaw PR Maintainer
|
||||
|
||||
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.
|
||||
@@ -41,28 +24,6 @@ gitcrawl cluster-detail openclaw/openclaw --id <cluster-id> --member-limit 20 --
|
||||
- `invalid`
|
||||
- `dirty` for PRs only
|
||||
|
||||
## Select small high-confidence triage candidates
|
||||
|
||||
When asked for `X` issues or PRs to triage, `X` means qualified candidates, not sampled threads.
|
||||
|
||||
Only list candidates that pass all gates:
|
||||
|
||||
- small owner/surface, with a likely narrow fix and focused regression test
|
||||
- symptom is reproducible or provable with logs, failing test, live command, dependency contract, or current-main behavior
|
||||
- root cause is traceable to code with file/line and the proposed fix touches that path
|
||||
- no strong smell that a broader refactor, ownership rethink, migration, or product decision is the better fix
|
||||
- dependency-backed behavior checked against upstream docs/source/types; live or web proof used when local proof is insufficient
|
||||
|
||||
Loop:
|
||||
|
||||
1. Use `gitcrawl` / `gh` to gather candidate clusters.
|
||||
2. Read issue/PR body, comments, current code, adjacent tests, and dependency contracts.
|
||||
3. Try focused repro or proof.
|
||||
4. Reject unclear, stale, speculative, broad-refactor, or owner-ambiguous items.
|
||||
5. Continue until `X` qualified candidates or the bounded search is exhausted.
|
||||
|
||||
Output only qualifying candidates, with: ref, surface, proof, cause, fix sketch, why small, expected test/gate. If none qualify, say so; do not pad.
|
||||
|
||||
## Enforce the bug-fix evidence bar
|
||||
|
||||
- Never merge a bug-fix PR based only on issue text, PR text, or AI rationale.
|
||||
@@ -74,21 +35,6 @@ Output only qualifying candidates, with: ref, surface, proof, cause, fix sketch,
|
||||
- If the claim is unsubstantiated or likely wrong, request evidence or changes instead of merging.
|
||||
- If the linked issue appears outdated or incorrect, correct triage first. Do not merge a speculative fix.
|
||||
|
||||
## Close low-signal manual PRs carefully
|
||||
|
||||
- Do not close for red CI alone. Require a clear low-signal category plus stale or failed validation.
|
||||
- Good manual-close categories:
|
||||
- blank or mostly untouched PR template with no concrete OpenClaw problem/fix
|
||||
- random docs-only churn such as root README translations, generic wording tweaks, or community-plugin discoverability docs that should go through ClawHub
|
||||
- test-only coverage without a linked bug, owner request, or behavior change
|
||||
- refactor-only cleanup, variable renames, formatting, or generated/baseline churn without maintainer request
|
||||
- third-party channel/provider/tool/skill/plugin work that belongs on ClawHub instead of core
|
||||
- risky ops/infra drive-bys such as new external CI services, release workflows, host upgrade scripts, Docker base migrations, or apt retry/fix-missing tweaks without owner request and green validation
|
||||
- dirty branches where a narrow stated change includes unrelated docs/generated/runtime/extension files
|
||||
- repeated bot-review spam or copied bot output without author-owned fixes
|
||||
- Keep or escalate plausible focused bug fixes, green PRs, active maintainer discussions, assigned work, recent author follow-up, and unique reproduction details.
|
||||
- For third-party capabilities, prefer the `r: third-party-extension` auto-response label when it applies; it points contributors to publish on ClawHub.
|
||||
|
||||
## Handle GitHub text safely
|
||||
|
||||
- For issue comments and PR comments, use literal multiline strings or `-F - <<'EOF'` for real newlines. Never embed `\n`.
|
||||
@@ -98,9 +44,9 @@ Output only qualifying candidates, with: ref, surface, proof, cause, fix sketch,
|
||||
|
||||
## 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:
|
||||
@@ -122,7 +68,6 @@ gh search issues --repo openclaw/openclaw --match title,body --limit 50 \
|
||||
- Keep commit messages concise and action-oriented.
|
||||
- Group related changes; avoid bundling unrelated refactors.
|
||||
- Use `.github/pull_request_template.md` for PR submissions and `.github/ISSUE_TEMPLATE/` for issues.
|
||||
- Do not commit PR-only artifacts such as screenshots under `.github/pr-assets`; attach them to the PR/comment or use an external artifact store instead.
|
||||
|
||||
## Extra safety
|
||||
|
||||
|
||||
@@ -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."
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-qa-testing
|
||||
description: Run, watch, debug, extend, or explain OpenClaw qa-lab and qa-channel scenarios, artifacts, and live lanes.
|
||||
description: Run, watch, debug, and extend OpenClaw QA testing with qa-lab and qa-channel. Use when Codex needs to execute the repo-backed QA suite, inspect live QA artifacts, debug failing scenarios, add new QA scenarios, or explain the OpenClaw QA workflow. Prefer the live OpenAI lane with regular openai/gpt-5.4 in fast mode; do not use gpt-5.4-pro or gpt-5.4-mini unless the user explicitly overrides that policy.
|
||||
---
|
||||
|
||||
# OpenClaw QA Testing
|
||||
@@ -49,97 +49,6 @@ pnpm openclaw qa suite \
|
||||
5. If the user wants to watch the live UI, find the current `openclaw-qa` listen port and report `http://127.0.0.1:<port>`.
|
||||
6. If a scenario fails, fix the product or harness root cause, then rerun the full lane.
|
||||
|
||||
## OTEL smoke
|
||||
|
||||
For local QA-lab OpenTelemetry validation, use:
|
||||
|
||||
```bash
|
||||
pnpm qa:otel:smoke
|
||||
```
|
||||
|
||||
This starts a local OTLP/HTTP trace receiver, runs the `otel-trace-smoke`
|
||||
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.
|
||||
- Quick auth check inside tmux:
|
||||
|
||||
```bash
|
||||
op account list
|
||||
```
|
||||
|
||||
- Direct Telegram npm live test secrets currently live in 1Password item:
|
||||
- vault: `OpenClaw`
|
||||
- item: `Telegram E2E`
|
||||
- That item is the first place to look for:
|
||||
- `OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN`
|
||||
- `OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN`
|
||||
- `OPENCLAW_QA_PROVIDER_MODE`
|
||||
- `OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC`
|
||||
- Convex QA secrets currently live in 1Password items:
|
||||
- vault: `OpenClaw`
|
||||
- item: `OPENCLAW_QA_CONVEX_SITE_URL`
|
||||
- item: `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER`
|
||||
- item: `OPENCLAW_QA_CONVEX_SECRET_CI`
|
||||
- Additional related notes/login items seen during QA credential work:
|
||||
- vault: `Private`
|
||||
- items: `OPENCLAW QA`, `Convex`, `Telegram`
|
||||
- If a required value is missing from those notes:
|
||||
- do not guess
|
||||
- ask the maintainer/operator for the current value or the current 1Password item name
|
||||
- for Telegram direct runs, `OPENCLAW_QA_TELEGRAM_GROUP_ID` may be stored separately from `Telegram E2E`
|
||||
- for Convex runs, the leased Telegram credential should provide the Telegram group id and bot tokens together; do not require a separate `OPENCLAW_QA_TELEGRAM_GROUP_ID`
|
||||
- for Convex runs, prefer `OpenClaw/OPENCLAW_QA_CONVEX_SITE_URL`; if that is stale or unclear, ask for the active pool URL before running
|
||||
- Prefer direct Telegram envs for the npm Telegram Docker lane when available:
|
||||
|
||||
```bash
|
||||
OPENCLAW_QA_TELEGRAM_GROUP_ID="..." \
|
||||
OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN="..." \
|
||||
OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN="..." \
|
||||
OPENCLAW_QA_PROVIDER_MODE="mock-openai" \
|
||||
OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC="openclaw@beta" \
|
||||
pnpm test:docker:npm-telegram-live
|
||||
```
|
||||
|
||||
- Prefer Convex mode when the goal is stable shared QA infra:
|
||||
- round-robin credential leasing
|
||||
- thinner wrapper for channel-specific setup
|
||||
- CLI/admin flows around the pooled credentials
|
||||
- Live npm Telegram Docker lane note:
|
||||
- `scripts/e2e/npm-telegram-live-runner.ts` reads `OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE`
|
||||
- do not assume `OPENCLAW_QA_PROVIDER_MODE` is consumed by that wrapper
|
||||
- if a 1Password note only gives `OPENCLAW_QA_PROVIDER_MODE`, map it explicitly to `OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE` before running the Docker lane
|
||||
- Verified live shape:
|
||||
- Convex mode can pass the real Docker lane without direct Telegram env vars
|
||||
- leased Telegram payload includes the group id coupled to the driver/SUT tokens
|
||||
- a real run of `pnpm test:docker:npm-telegram-live` passed with:
|
||||
- `OPENCLAW_QA_CREDENTIAL_SOURCE=convex`
|
||||
- `OPENCLAW_QA_CREDENTIAL_ROLE=maintainer`
|
||||
- `OPENCLAW_QA_CONVEX_SITE_URL`
|
||||
- `OPENCLAW_QA_CONVEX_SECRET_MAINTAINER`
|
||||
- `OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE=mock-openai`
|
||||
|
||||
## Character evals
|
||||
|
||||
Use `qa character-eval` for style/persona/vibe checks across multiple live models.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-release-maintainer
|
||||
description: Prepare or verify OpenClaw stable/beta releases, changelogs, release notes, publish commands, and artifacts.
|
||||
description: Maintainer workflow for OpenClaw releases, prereleases, changelog release notes, and publish validation. Use when Codex needs to prepare or verify stable or beta release steps, align version naming, assemble release notes, check release auth requirements, or validate publish-time commands and artifacts.
|
||||
---
|
||||
|
||||
# OpenClaw Release Maintainer
|
||||
@@ -25,38 +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 their matching npm package has been
|
||||
published. If a pushed beta tag fails preflight before npm publish, delete and
|
||||
recreate the tag and prerelease at the fixed commit so npm prerelease versions
|
||||
stay contiguous. If a published 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.
|
||||
- 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 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
|
||||
@@ -93,27 +70,6 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
- Every stable OpenClaw release ships the npm package and macOS app together.
|
||||
Beta releases normally ship npm/package artifacts first and skip mac app
|
||||
build/sign/notarize unless the operator requests mac beta validation.
|
||||
- Do not let the slower macOS signing/notary path block npm publication once
|
||||
the npm preflight has passed. Keep mac validation/publish running in
|
||||
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
|
||||
fixes after the stable npm package or GitHub tag is already published, do not
|
||||
create a `vYYYY.M.D-N` correction tag just to change the workflow source.
|
||||
Dispatch the private mac workflows for the original `tag=vYYYY.M.D` with
|
||||
`source_ref=release/YYYY.M.D` and `public_release_branch=release/YYYY.M.D`;
|
||||
provenance checks must prove the source SHA descends from the tag and
|
||||
validation/preflight use the same source. Reserve `vYYYY.M.D-N` correction
|
||||
tags for emergency hotfixes that must publish a new npm package/release
|
||||
identity, not for ordinary mac-only packaging recovery.
|
||||
- The production Sparkle feed lives at `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`, and the canonical published file is `appcast.xml` on `main` in the `openclaw` repo.
|
||||
- That shared production Sparkle feed is stable-only. Beta mac releases may
|
||||
upload assets to the GitHub prerelease, but they must not replace the shared
|
||||
@@ -125,23 +81,7 @@ Use this skill for release and publish-time workflow. Keep ordinary development
|
||||
|
||||
## Build changelog-backed release notes
|
||||
|
||||
- Before release branching or tagging, rewrite the target `CHANGELOG.md`
|
||||
section from commit history, not just from existing notes: scan commits since
|
||||
the last reachable release tag, add missed user-facing changes, dedupe
|
||||
overlapping entries, and sort each section from most to least interesting for
|
||||
users.
|
||||
- Changelog entries should be user-facing, not internal release-process notes.
|
||||
- GitHub release and prerelease bodies must use the full matching
|
||||
`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`
|
||||
@@ -164,36 +104,14 @@ live`; keep it clearly beta and avoid implying stable promotion.
|
||||
- Lead with user-visible capabilities, then important integrations, then
|
||||
reliability/security/install fixes. Compress "lots of fixes" into one
|
||||
readable bullet.
|
||||
- Read the full changelog section before drafting. Do not lead with coverage,
|
||||
CI, validation, or internal release mechanics unless the release is explicitly
|
||||
about those. Peter prefers concrete user wins: features, integrations,
|
||||
workflow improvements, and practical reliability fixes.
|
||||
- Tone: high-signal, slightly cheeky, confident, not corporate. One joke is
|
||||
enough. Avoid punching down, insulting users, or promising what was not
|
||||
verified.
|
||||
- Peter likes dry, compact taglines when they feel earned. Good example:
|
||||
`Big release, tiny release notes... kidding.` Keep the joke short and let the
|
||||
feature bullets carry the tweet; do not turn the punchline into a second
|
||||
paragraph or a forced bit.
|
||||
- Length: release tweets are always standard tweets under 280 characters, with
|
||||
room for one URL. Trim to 3-4 bullets and count the final text before posting.
|
||||
- Links/media: include the GitHub release or changelog link at the end of the
|
||||
first release tweet.
|
||||
- Thread follow-ups: if doing a thread, keep the first release tweet as the
|
||||
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
|
||||
spot; under 280 is the hard cap. If a URL makes a tweet fail, trim prose
|
||||
before dropping the URL.
|
||||
Prefer explaining diagnostics, trajectory/export, provider setup, model
|
||||
commands, or other setup-heavy features in follow-ups instead of overloading
|
||||
the first release tweet.
|
||||
- Length: release tweets are always standard tweets under 280 characters. Trim
|
||||
to 3-4 bullets and count the final text before posting.
|
||||
- Links/media: include the GitHub release or changelog link at the end. Add a
|
||||
short docs follow-up reply only when there is a standout feature that needs
|
||||
setup instructions.
|
||||
- Hotfix/correction: be direct and accountable. State what slipped, what is
|
||||
fixed, and the new version. Keep jokes out of incident-style posts.
|
||||
|
||||
@@ -240,16 +158,10 @@ Before tagging or publishing, run:
|
||||
pnpm check:architecture
|
||||
pnpm build
|
||||
pnpm ui:build
|
||||
pnpm qa:otel:smoke
|
||||
pnpm release:check
|
||||
pnpm test:install:smoke
|
||||
```
|
||||
|
||||
- Use `pnpm qa:otel:smoke` when release validation needs telemetry coverage.
|
||||
It starts a local OTLP/HTTP trace receiver, runs QA-lab's
|
||||
`otel-trace-smoke`, and checks span names plus content/identifier redaction
|
||||
without external Opik or Langfuse credentials.
|
||||
|
||||
For a non-root smoke path:
|
||||
|
||||
```bash
|
||||
@@ -289,18 +201,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Source Peter's profile before live release validation so OpenAI and Anthropic
|
||||
credentials are available without printing secrets:
|
||||
`set -a; source "$HOME/.profile"; set +a`.
|
||||
- Parallels validation and any local live model QA for this train must use both
|
||||
- Release QA and Parallels validation for this train must use both
|
||||
`OPENAI_API_KEY` and `ANTHROPIC_API_KEY`. If either is missing after sourcing
|
||||
`.profile`, stop before starting those local long lanes and report the
|
||||
missing key.
|
||||
- Live credentialed channel QA is the GitHub Actions workflow
|
||||
`QA-Lab - All Lanes` (`.github/workflows/qa-live-telegram-convex.yml`), not a
|
||||
local substitute. Dispatch it from Actions against the release tag and wait
|
||||
for it to pass before npm preflight/publish readiness. Use a SHA only when it
|
||||
satisfies the workflow's secret-bearing trust gate: main ancestor or open PR
|
||||
head. It runs the QA Lab mock parity gate plus live Matrix and live Telegram
|
||||
lanes using the `qa-live-shared` environment; Telegram uses Convex CI
|
||||
credential leases.
|
||||
`.profile`, stop before starting the long lanes and report the missing key.
|
||||
- Default release checks:
|
||||
- `pnpm check`
|
||||
- `pnpm check:test-types`
|
||||
@@ -318,44 +221,23 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- all Parallels install/update tests:
|
||||
`pnpm test:parallels:npm-update -- --json` plus any needed individual
|
||||
rerun lanes from `openclaw-parallels-smoke`
|
||||
- all QA release validation: dispatch GitHub Actions > `QA-Lab - All Lanes`
|
||||
against the release tag and require success. This is the release gate for
|
||||
live credentialed Matrix/Telegram channel coverage. Use a SHA only when it
|
||||
satisfies the workflow trust gate. Run local OpenAI/Anthropic suites or
|
||||
repo-backed character evals only when the operator asks for extra model
|
||||
coverage or a failure needs local debugging.
|
||||
- all QA release validation:
|
||||
OpenAI live suite with `openai/gpt-5.4` in fast mode, Anthropic live suite
|
||||
with `anthropic/claude-opus-4-6`, and the repo-backed character evals
|
||||
- Post-published beta verification roster:
|
||||
- `node --import tsx scripts/openclaw-npm-postpublish-verify.ts <beta-version>`
|
||||
- install/update smoke against the published beta channel
|
||||
- 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.
|
||||
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.
|
||||
- Parallels published beta install/update coverage with both OpenAI and
|
||||
Anthropic provider keys available
|
||||
- Parallels install/update proof must keep plugin installs enabled unless the
|
||||
operator explicitly scopes a harness-only isolation check; a lane that
|
||||
disables bundled plugin installs is not valid plugin/dependency release
|
||||
evidence.
|
||||
- targeted QA reruns only for areas touched by fixes after the full pre-npm
|
||||
roster, unless the operator requests the full QA roster again. If the fix
|
||||
touches live channel QA, credential plumbing, Matrix, Telegram, or the QA
|
||||
harness, rerun Actions > `QA-Lab - All Lanes`.
|
||||
roster, unless the operator requests the full QA roster again
|
||||
- Check all release-related build surfaces touched by the release, not only the npm package.
|
||||
- For beta-style full e2e batteries, hard-cap top-level long lanes instead of letting them run indefinitely. Use host `timeout --foreground`/`gtimeout --foreground` caps such as:
|
||||
- `45m` for `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`
|
||||
- `90m` for `pnpm test:docker:all`
|
||||
- `60m` each for standalone Docker live lanes
|
||||
- `180m` for local full QA live OpenAI + Anthropic rosters when explicitly
|
||||
requested; the default release channel QA gate is Actions >
|
||||
`QA-Lab - All Lanes`
|
||||
- `180m` for the full QA live OpenAI + Anthropic roster
|
||||
- Parallels caps from the `openclaw-parallels-smoke` skill
|
||||
If a lane hits its cap, stop and inspect/fix the affected lane before continuing; do not continue to wait on the same process.
|
||||
- Actual npm install/update phases are capped at 5 minutes. If `npm install -g`, installer package install, or `openclaw update` takes longer than 300s in release e2e, stop treating the run as healthy progress and debug the installer/updater or harness.
|
||||
@@ -369,22 +251,13 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Any fix after preflight means a new commit. Delete and recreate the tag and
|
||||
matching GitHub release from the fixed commit, then rerun preflight from
|
||||
scratch before publishing.
|
||||
Exception: never delete or recreate a beta tag whose matching npm package has
|
||||
already been published; increment to the next beta number instead. If only the
|
||||
pushed tag/prerelease exists and npm publish has not happened, recreate that
|
||||
same beta tag at the fixed commit.
|
||||
Exception: never delete or recreate a beta tag that has already been pushed or
|
||||
published; increment to the next beta number instead.
|
||||
- For stable mac releases, generate the signed `appcast.xml` before uploading
|
||||
public release assets so the updater feed cannot lag the published binaries.
|
||||
- Serialize stable appcast-producing runs across tags so two releases do not
|
||||
generate replacement `appcast.xml` files from the same stale seed.
|
||||
- For stable releases, rely primarily on the latest beta's broader release
|
||||
workflow confidence. When promoting the matching non-beta build to npm
|
||||
`latest`, prefer a light time-bounded verification pass: published npm
|
||||
postpublish verify, Docker install/update smoke, macOS-only Parallels
|
||||
install/update smoke, and required QA signal. Do not rerun the full
|
||||
Docker/Parallels matrix unless the beta evidence is stale, the stable build
|
||||
differs materially from beta, or the operator explicitly asks for full
|
||||
retesting.
|
||||
- For stable releases, confirm the latest beta already passed the broader release workflows before cutting stable.
|
||||
- If any required build, packaging step, or release workflow is red, do not say the release is ready.
|
||||
|
||||
## Use the right auth flow
|
||||
@@ -394,29 +267,6 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow because `npm dist-tag` management needs `NPM_TOKEN`, while the
|
||||
public npm release workflow stays OIDC-only.
|
||||
- Prefer fixing the private workflow token path over any local 1Password
|
||||
fallback. The desired setup is a granular npm token stored as the private
|
||||
repo's `NPM_TOKEN` secret, scoped to the `openclaw` package with read/write
|
||||
and 2FA bypass for automation.
|
||||
- If the private dist-tag workflow cannot promote because `NPM_TOKEN` is absent
|
||||
or stale, use the local tmux + 1Password fallback:
|
||||
- Start or reuse a tmux session so interactive `npm login` and OTP prompts
|
||||
are observable and recoverable.
|
||||
- Hard rule: never run `op` directly in the main agent shell during release
|
||||
work. Any 1Password CLI use must happen inside that tmux session so prompts
|
||||
and alerts are contained and observable.
|
||||
- Use the 1Password item `op://Private/Npmjs` for npm credentials and OTP.
|
||||
Do not print passwords, tokens, or OTPs to the transcript; send them through
|
||||
tmux buffers, env vars scoped to the tmux command, or `expect` with
|
||||
`log_user 0`.
|
||||
- Re-authenticate npm inside that tmux session with
|
||||
`npm login --auth-type=legacy`, then confirm `npm whoami` reports
|
||||
`steipete`.
|
||||
- Promote with a fresh OTP:
|
||||
`npm dist-tag add openclaw@YYYY.M.D latest --otp "$OTP"`.
|
||||
- Verify with a cache-bypassed registry read, for example:
|
||||
`npm view openclaw dist-tags --json --prefer-online --cache /tmp/openclaw-npm-cache-verify-$$`
|
||||
and `npm view openclaw@latest version dist.tarball --json --prefer-online`.
|
||||
- Direct stable publishes can also use that private dist-tag workflow to point
|
||||
`beta` at the already-published `latest` version when the operator wants both
|
||||
tags aligned immediately.
|
||||
@@ -533,93 +383,73 @@ 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 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.
|
||||
11. Confirm the target npm version is not already published.
|
||||
12. Create and push the git tag from the release branch.
|
||||
13. Create or refresh the matching GitHub release.
|
||||
14. Dispatch Actions > `QA-Lab - All Lanes` against the release tag and wait
|
||||
for the mock parity, live Matrix, and live Telegram credentialed-channel
|
||||
lanes to pass.
|
||||
15. Start `.github/workflows/openclaw-npm-release.yml` from the release branch
|
||||
14. Start `.github/workflows/openclaw-npm-release.yml` from the release branch
|
||||
with `preflight_only=true`
|
||||
and choose the intended `npm_dist_tag` (`beta` default; `latest` only for
|
||||
an intentional direct stable publish). Wait for it to pass. Save that run id
|
||||
because the real publish requires it to reuse the prepared npm tarball.
|
||||
16. For stable releases, start `.github/workflows/macos-release.yml` in
|
||||
15. For stable releases, start `.github/workflows/macos-release.yml` in
|
||||
`openclaw/openclaw` and wait for the public validation-only run to pass.
|
||||
17. For stable releases, start
|
||||
16. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
|
||||
with the same tag and wait for the private mac validation lane to pass.
|
||||
18. For stable releases, start
|
||||
17. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
with `preflight_only=true` and wait for it to pass. Save that run id because
|
||||
the real publish requires it to reuse the notarized mac artifacts.
|
||||
19. If any preflight or validation run fails, fix the issue on a new commit,
|
||||
18. If any preflight or validation run fails, fix the issue on a new commit,
|
||||
delete the tag and matching GitHub release, recreate them from the fixed
|
||||
commit, and rerun all relevant preflights from scratch before continuing.
|
||||
Never reuse old preflight results after the commit changes. For pushed or
|
||||
published beta tags, do not delete/recreate; increment to the next beta tag.
|
||||
For preflight-only failures where npm did not publish the beta version,
|
||||
delete/recreate the same beta tag and prerelease at the fixed commit instead
|
||||
of skipping a prerelease number.
|
||||
20. Start `.github/workflows/openclaw-npm-release.yml` from the same branch with
|
||||
19. Start `.github/workflows/openclaw-npm-release.yml` from the same branch with
|
||||
the same tag for the real publish, choose `npm_dist_tag` (`beta` default,
|
||||
`latest` only when you intentionally want direct stable publish), keep it
|
||||
the same as the preflight run, and pass the successful npm
|
||||
`preflight_run_id`.
|
||||
21. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
22. Run postpublish verification:
|
||||
20. Wait for `npm-release` approval from `@openclaw/openclaw-release-managers`.
|
||||
21. 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 package is 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 >
|
||||
`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
|
||||
authorized beta-attempt limit, normally 4.
|
||||
24. Announce the beta/stable release on Discord best-effort using Peter's bot
|
||||
22. 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. 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 authorized beta-attempt limit, normally 4.
|
||||
23. Announce the beta/stable release on Discord best-effort using Peter's bot
|
||||
token from `.profile`.
|
||||
25. If the operator requested beta only, stop after beta verification and the
|
||||
24. If the operator requested beta only, stop after beta verification and the
|
||||
announcement.
|
||||
26. If the stable release was published to `beta`, use the light stable
|
||||
promotion roster when the matching beta already carried the full confidence
|
||||
pass: published npm postpublish verify, Docker install/update smoke,
|
||||
macOS-only Parallels install/update smoke, and required QA signal.
|
||||
Then start the private
|
||||
25. If the stable release was published to `beta`, start the private
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow to promote that stable version from `beta` to `latest`, then
|
||||
verify `latest` now points at that version.
|
||||
27. If the stable release was published directly to `latest` and `beta` should
|
||||
workflow after beta validation passes to promote that stable version from
|
||||
`beta` to `latest`, then verify `latest` now points at that version.
|
||||
26. If the stable release was published directly to `latest` and `beta` should
|
||||
follow it, start that same private dist-tag workflow to point `beta` at the
|
||||
stable version, then verify both `latest` and `beta` point at that version.
|
||||
28. For stable releases, start
|
||||
27. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
for the real publish with the successful private mac `preflight_run_id` and
|
||||
wait for success.
|
||||
29. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
|
||||
28. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
|
||||
and `.dSYM.zip` artifacts to the existing GitHub release in
|
||||
`openclaw/openclaw`.
|
||||
30. For stable releases, download `macos-appcast-<tag>` from the successful
|
||||
29. For stable releases, download `macos-appcast-<tag>` from the successful
|
||||
private mac run, update `appcast.xml` on `main`, and verify the feed. Merge
|
||||
or cherry-pick release branch changes back to `main` after stable succeeds.
|
||||
31. For beta releases, publish the mac assets only when intentionally requested;
|
||||
30. For beta releases, publish the mac assets only when intentionally requested;
|
||||
expect no shared production
|
||||
`appcast.xml` artifact and do not update the shared production feed unless a
|
||||
separate beta feed exists.
|
||||
32. After publish, verify npm and the attached release artifacts.
|
||||
31. After publish, verify npm and the attached release artifacts.
|
||||
|
||||
## GHSA advisory work
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-secret-scanning-maintainer
|
||||
description: Triage, redact, clean up, and resolve OpenClaw GitHub Secret Scanning alerts in issues or PRs.
|
||||
description: Maintainer-only workflow for handling GitHub Secret Scanning alerts on OpenClaw. Use when Codex needs to triage, redact, clean up, and resolve secret leakage found in issue comments, issue bodies, PR comments, or other GitHub content.
|
||||
---
|
||||
|
||||
# OpenClaw Secret Scanning Maintainer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: openclaw-test-heap-leaks
|
||||
description: Investigate OpenClaw pnpm test memory growth, Vitest OOMs, RSS spikes, and heap snapshot deltas.
|
||||
description: Investigate `pnpm test` memory growth, Vitest worker OOMs, and suspicious RSS increases in OpenClaw using the `scripts/test-parallel.mjs` heap snapshot tooling. Use when Codex needs to reproduce test-lane memory growth, collect repeated `.heapsnapshot` files, compare snapshots from the same worker PID, triage likely transformed-module retention versus likely runtime leaks, and fix or reduce the impact by patching cleanup logic or isolating hotspot tests.
|
||||
---
|
||||
|
||||
# OpenClaw Test Heap Leaks
|
||||
|
||||
@@ -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 performance without losing coverage. Use when Codex needs to reassess `pnpm test`, compare grouped Vitest reports, identify CPU/memory/import hotspots, fix slow tests or cold runtime paths, preserve behavior proofs, update the performance report, add AGENTS guardrails, and make scoped commits/pushes for OpenClaw test-speed work.
|
||||
---
|
||||
|
||||
# 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: optimizetests
|
||||
description: Optimize OpenClaw slow tests, imports, misplaced coverage, and CI wall time without dropping coverage.
|
||||
description: Optimize OpenClaw test runtime end to end. Use when the user asks for /optimizetests, slow-test review, import optimization, deduping tests, moving misplaced core coverage to extensions, or reducing CI/test wall time without adding shards or dropping coverage.
|
||||
---
|
||||
|
||||
# Optimize Tests
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: parallels-discord-roundtrip
|
||||
description: Run macOS Parallels smoke with Discord send, host verification, host reply, and guest readback proof.
|
||||
description: Run the macOS Parallels smoke harness with Discord end-to-end roundtrip verification, including guest send, host verification, host reply, and guest readback.
|
||||
---
|
||||
|
||||
# Parallels Discord Roundtrip
|
||||
@@ -50,7 +50,6 @@ pnpm test:parallels:macos \
|
||||
- Avoid `prlctl enter` / expect for long Discord setup scripts; it line-wraps/corrupts long commands. Use `prlctl exec --current-user /bin/sh -lc ...` for the Discord config phase.
|
||||
- Full 3-OS sweeps: the shared build lock is safe in parallel, but snapshot restore is still a Parallels bottleneck. Prefer serialized Windows/Linux restore-heavy reruns if the host is already under load.
|
||||
- Harness cleanup deletes the temporary Discord smoke messages at exit.
|
||||
- After a successful Discord roundtrip, shut down the macOS guest before handoff (`prlctl stop "macOS Tahoe"`). The macOS smoke harness should do this automatically after successful Discord proof; still stop the VM manually after ad-hoc Discord checks. Do not leave the Discord-configured VM running; it can keep reading/posting in `#maintainer` and spam Discord after the proof is complete.
|
||||
- Per-phase logs: `/tmp/openclaw-parallels-smoke.*`
|
||||
- Machine summary: pass `--json`
|
||||
- If roundtrip flakes, inspect `fresh.discord-roundtrip.log` and `discord-last-readback.json` in the run dir first.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: security-triage
|
||||
description: Triage OpenClaw security advisories, drafts, and GHSA reports with shipped-tag and trust-model proof.
|
||||
description: Triage GitHub security advisories for OpenClaw with high-confidence close/keep decisions, exact tag and commit verification, trust-model checks, optional hardening notes, and a final reply ready to post and copy to clipboard.
|
||||
---
|
||||
|
||||
# Security Triage
|
||||
@@ -45,17 +45,6 @@ For each advisory, decide:
|
||||
- `keep open`
|
||||
- `keep open but narrow`
|
||||
|
||||
Default to one advisory at a time when comments/closures are involved:
|
||||
|
||||
1. Review exactly one GHSA.
|
||||
2. Print the GHSA URL first.
|
||||
3. Summarize the decision and evidence for discussion.
|
||||
4. Draft one maintainer-ready comment.
|
||||
5. Copy only that one comment to the clipboard.
|
||||
6. Stop and wait for Peter to post/discuss before moving to the next GHSA.
|
||||
|
||||
Do not batch multiple close comments unless Peter explicitly asks for a batch.
|
||||
|
||||
Check in this order:
|
||||
|
||||
1. Trust model
|
||||
@@ -71,11 +60,6 @@ Check in this order:
|
||||
4. Functional tradeoff
|
||||
- If a hardening change would reduce intended user functionality, call that out before proposing it.
|
||||
- Prefer fixes that preserve user workflows over deny-by-default regressions unless the boundary demands it.
|
||||
5. Hardening follow-up
|
||||
- Even when the GHSA should close, ask whether a narrow hardening change would reduce footguns without changing the documented trust boundary.
|
||||
- Separate hardening from vulnerability status. Phrase it as "not required for GHSA closure, but worth considering".
|
||||
- Bring up hardening only if it is concrete, low-risk, and preserves intended maintainer/operator workflows.
|
||||
- If hardening would require a product/security model change, say that explicitly and do not imply it is a required fix for closure.
|
||||
|
||||
## Response Format
|
||||
|
||||
@@ -92,22 +76,9 @@ When preparing a maintainer-ready close reply:
|
||||
|
||||
Keep tone firm, specific, non-defensive.
|
||||
|
||||
## Discussion Mode
|
||||
|
||||
When Peter is manually posting GHSA comments, use this flow:
|
||||
|
||||
1. Show the URL.
|
||||
2. Give a terse verdict (`close`, `keep open`, or `keep open but narrow`).
|
||||
3. List the strongest evidence bullets.
|
||||
4. State any optional hardening follow-up separately from the close reason.
|
||||
5. Copy the proposed comment body with `pbcopy`.
|
||||
6. End the reply after the one advisory. Do not continue to the next advisory until Peter says to continue.
|
||||
|
||||
If the GitHub API cannot post comments for private advisories, say so once and keep using clipboard/UI paste.
|
||||
|
||||
## Clipboard Step
|
||||
|
||||
After drafting the final post body for the current advisory, copy it:
|
||||
After drafting the final post body, copy it:
|
||||
|
||||
```bash
|
||||
pbcopy <<'EOF'
|
||||
@@ -115,7 +86,7 @@ pbcopy <<'EOF'
|
||||
EOF
|
||||
```
|
||||
|
||||
Tell the user that the clipboard now contains the proposed response for that advisory.
|
||||
Tell the user that the clipboard now contains the proposed response.
|
||||
|
||||
## Useful Commands
|
||||
|
||||
|
||||
@@ -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: Maintainer workflow for deciding whether an OpenClaw pull request or issue is a duplicate, gathering evidence with ghreplica and pr-search-cli, grouping related work in prtags, and syncing the duplicate grouping back to GitHub through prtags. Use when Codex needs to search for duplicate PRs or issues, create or reuse a duplicate group, enforce one-group-per-target discipline, save duplicate judgments in prtags, or prepare group state for comment sync.
|
||||
---
|
||||
|
||||
# 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."
|
||||
|
||||
@@ -8,14 +8,6 @@
|
||||
|
||||
.bun-cache
|
||||
.bun
|
||||
.artifacts
|
||||
**/.artifacts
|
||||
.local
|
||||
**/.local
|
||||
.pi
|
||||
**/.pi
|
||||
__openclaw_vitest__
|
||||
**/__openclaw_vitest__
|
||||
.tmp
|
||||
**/.tmp
|
||||
.DS_Store
|
||||
@@ -46,9 +38,6 @@ docs/.generated
|
||||
*.log
|
||||
tmp
|
||||
**/tmp
|
||||
dist-runtime
|
||||
**/dist-runtime
|
||||
openclaw-path-alias-*
|
||||
|
||||
# build artifacts
|
||||
dist
|
||||
|
||||
@@ -82,5 +82,4 @@ OPENCLAW_GATEWAY_TOKEN=
|
||||
|
||||
# ELEVENLABS_API_KEY=...
|
||||
# XI_API_KEY=... # alias for ElevenLabs
|
||||
# INWORLD_API_KEY=...
|
||||
# DEEPGRAM_API_KEY=...
|
||||
|
||||
84
.github/CODEOWNERS
vendored
84
.github/CODEOWNERS
vendored
@@ -2,51 +2,49 @@
|
||||
/.github/CODEOWNERS @steipete
|
||||
|
||||
# WARNING: GitHub CODEOWNERS uses last-match-wins semantics.
|
||||
# If you add overlapping rules below the secops block, include @openclaw/openclaw-secops
|
||||
# If you add overlapping rules below the secops block, include @openclaw/secops
|
||||
# on those entries too or you can silently remove required secops review.
|
||||
# Security-sensitive code, config, and docs require secops review.
|
||||
/SECURITY.md @openclaw/openclaw-secops
|
||||
/.github/dependabot.yml @openclaw/openclaw-secops
|
||||
/.github/codeql/ @openclaw/openclaw-secops
|
||||
/.github/workflows/codeql.yml @openclaw/openclaw-secops
|
||||
/.github/workflows/codeql-android-critical-security.yml @openclaw/openclaw-secops
|
||||
/.github/workflows/codeql-critical-quality.yml @openclaw/openclaw-secops
|
||||
/src/security/ @openclaw/openclaw-secops
|
||||
/src/secrets/ @openclaw/openclaw-secops
|
||||
/src/config/*secret*.ts @openclaw/openclaw-secops
|
||||
/src/config/**/*secret*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/*auth*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/**/*auth*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/*secret*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/**/*secret*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/security-path*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/resolve-configured-secret-input-string*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/protocol/**/*secret*.ts @openclaw/openclaw-secops
|
||||
/src/gateway/server-methods/secrets*.ts @openclaw/openclaw-secops
|
||||
/src/agents/*auth*.ts @openclaw/openclaw-secops
|
||||
/src/agents/**/*auth*.ts @openclaw/openclaw-secops
|
||||
/src/agents/auth-profiles*.ts @openclaw/openclaw-secops
|
||||
/src/agents/auth-health*.ts @openclaw/openclaw-secops
|
||||
/src/agents/auth-profiles/ @openclaw/openclaw-secops
|
||||
/src/agents/sandbox.ts @openclaw/openclaw-secops
|
||||
/src/agents/sandbox-*.ts @openclaw/openclaw-secops
|
||||
/src/agents/sandbox/ @openclaw/openclaw-secops
|
||||
/src/infra/secret-file*.ts @openclaw/openclaw-secops
|
||||
/src/cron/stagger.ts @openclaw/openclaw-secops
|
||||
/src/cron/service/jobs.ts @openclaw/openclaw-secops
|
||||
/docs/security/ @openclaw/openclaw-secops
|
||||
/docs/gateway/authentication.md @openclaw/openclaw-secops
|
||||
/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md @openclaw/openclaw-secops
|
||||
/docs/gateway/sandboxing.md @openclaw/openclaw-secops
|
||||
/docs/gateway/secrets-plan-contract.md @openclaw/openclaw-secops
|
||||
/docs/gateway/secrets.md @openclaw/openclaw-secops
|
||||
/docs/gateway/security/ @openclaw/openclaw-secops
|
||||
/docs/cli/approvals.md @openclaw/openclaw-secops
|
||||
/docs/cli/sandbox.md @openclaw/openclaw-secops
|
||||
/docs/cli/security.md @openclaw/openclaw-secops
|
||||
/docs/cli/secrets.md @openclaw/openclaw-secops
|
||||
/docs/reference/secretref-credential-surface.md @openclaw/openclaw-secops
|
||||
/docs/reference/secretref-user-supplied-credentials-matrix.json @openclaw/openclaw-secops
|
||||
/SECURITY.md @openclaw/secops
|
||||
/.github/dependabot.yml @openclaw/secops
|
||||
/.github/codeql/ @openclaw/secops
|
||||
/.github/workflows/codeql.yml @openclaw/secops
|
||||
/src/security/ @openclaw/secops
|
||||
/src/secrets/ @openclaw/secops
|
||||
/src/config/*secret*.ts @openclaw/secops
|
||||
/src/config/**/*secret*.ts @openclaw/secops
|
||||
/src/gateway/*auth*.ts @openclaw/secops
|
||||
/src/gateway/**/*auth*.ts @openclaw/secops
|
||||
/src/gateway/*secret*.ts @openclaw/secops
|
||||
/src/gateway/**/*secret*.ts @openclaw/secops
|
||||
/src/gateway/security-path*.ts @openclaw/secops
|
||||
/src/gateway/resolve-configured-secret-input-string*.ts @openclaw/secops
|
||||
/src/gateway/protocol/**/*secret*.ts @openclaw/secops
|
||||
/src/gateway/server-methods/secrets*.ts @openclaw/secops
|
||||
/src/agents/*auth*.ts @openclaw/secops
|
||||
/src/agents/**/*auth*.ts @openclaw/secops
|
||||
/src/agents/auth-profiles*.ts @openclaw/secops
|
||||
/src/agents/auth-health*.ts @openclaw/secops
|
||||
/src/agents/auth-profiles/ @openclaw/secops
|
||||
/src/agents/sandbox.ts @openclaw/secops
|
||||
/src/agents/sandbox-*.ts @openclaw/secops
|
||||
/src/agents/sandbox/ @openclaw/secops
|
||||
/src/infra/secret-file*.ts @openclaw/secops
|
||||
/src/cron/stagger.ts @openclaw/secops
|
||||
/src/cron/service/jobs.ts @openclaw/secops
|
||||
/docs/security/ @openclaw/secops
|
||||
/docs/gateway/authentication.md @openclaw/secops
|
||||
/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md @openclaw/secops
|
||||
/docs/gateway/sandboxing.md @openclaw/secops
|
||||
/docs/gateway/secrets-plan-contract.md @openclaw/secops
|
||||
/docs/gateway/secrets.md @openclaw/secops
|
||||
/docs/gateway/security/ @openclaw/secops
|
||||
/docs/cli/approvals.md @openclaw/secops
|
||||
/docs/cli/sandbox.md @openclaw/secops
|
||||
/docs/cli/security.md @openclaw/secops
|
||||
/docs/cli/secrets.md @openclaw/secops
|
||||
/docs/reference/secretref-credential-surface.md @openclaw/secops
|
||||
/docs/reference/secretref-user-supplied-credentials-matrix.json @openclaw/secops
|
||||
|
||||
# Release workflow and its supporting release-path checks.
|
||||
/.github/workflows/openclaw-npm-release.yml @openclaw/openclaw-release-managers
|
||||
|
||||
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
|
||||
11
.github/actions/setup-node-env/action.yml
vendored
11
.github/actions/setup-node-env/action.yml
vendored
@@ -37,7 +37,6 @@ runs:
|
||||
check-latest: false
|
||||
|
||||
- name: Setup pnpm + cache store
|
||||
id: pnpm-cache
|
||||
uses: ./.github/actions/setup-pnpm-store-cache
|
||||
with:
|
||||
pnpm-version: ${{ inputs.pnpm-version }}
|
||||
@@ -90,21 +89,11 @@ 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")
|
||||
fi
|
||||
pnpm "${install_args[@]}" || pnpm "${install_args[@]}"
|
||||
|
||||
- name: Save pnpm store cache
|
||||
if: inputs.install-deps == 'true' && steps.pnpm-cache.outputs.cache-enabled == 'true' && steps.pnpm-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.store-path }}
|
||||
key: ${{ steps.pnpm-cache.outputs.primary-key }}
|
||||
|
||||
@@ -14,25 +14,9 @@ inputs:
|
||||
required: false
|
||||
default: "true"
|
||||
use-actions-cache:
|
||||
description: Whether to restore pnpm store with actions/cache.
|
||||
description: Whether to restore/save pnpm store with actions/cache.
|
||||
required: false
|
||||
default: "true"
|
||||
outputs:
|
||||
cache-enabled:
|
||||
description: Whether actions/cache restore was enabled.
|
||||
value: ${{ steps.pnpm-cache-config.outputs.enabled }}
|
||||
cache-hit:
|
||||
description: Whether the pnpm store cache had an exact key hit.
|
||||
value: ${{ steps.pnpm-cache-restore.outputs.cache-hit }}
|
||||
cache-matched-key:
|
||||
description: Cache key matched by restore, if any.
|
||||
value: ${{ steps.pnpm-cache-restore.outputs.cache-matched-key }}
|
||||
primary-key:
|
||||
description: Primary pnpm store cache key.
|
||||
value: ${{ steps.pnpm-cache-config.outputs.primary-key }}
|
||||
store-path:
|
||||
description: Resolved pnpm store path.
|
||||
value: ${{ steps.pnpm-store.outputs.path }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -62,29 +46,18 @@ runs:
|
||||
shell: bash
|
||||
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve pnpm store cache keys
|
||||
id: pnpm-cache-config
|
||||
shell: bash
|
||||
env:
|
||||
CACHE_KEY_SUFFIX: ${{ inputs.cache-key-suffix }}
|
||||
LOCKFILE_HASH: ${{ hashFiles('pnpm-lock.yaml') }}
|
||||
USE_ACTIONS_CACHE: ${{ inputs.use-actions-cache }}
|
||||
USE_RESTORE_KEYS: ${{ inputs.use-restore-keys }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "enabled=$USE_ACTIONS_CACHE" >> "$GITHUB_OUTPUT"
|
||||
echo "primary-key=${RUNNER_OS}-pnpm-store-${CACHE_KEY_SUFFIX}-${LOCKFILE_HASH}" >> "$GITHUB_OUTPUT"
|
||||
if [ "$USE_RESTORE_KEYS" = "true" ]; then
|
||||
echo "restore-keys=${RUNNER_OS}-pnpm-store-${CACHE_KEY_SUFFIX}-" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "restore-keys=" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Restore pnpm store cache
|
||||
id: pnpm-cache-restore
|
||||
if: inputs.use-actions-cache == 'true'
|
||||
uses: actions/cache/restore@v5
|
||||
- name: Restore pnpm store cache (exact key only)
|
||||
if: inputs.use-actions-cache == 'true' && inputs.use-restore-keys != 'true'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.path }}
|
||||
key: ${{ steps.pnpm-cache-config.outputs.primary-key }}
|
||||
restore-keys: ${{ steps.pnpm-cache-config.outputs.restore-keys }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore pnpm store cache (with fallback keys)
|
||||
if: inputs.use-actions-cache == 'true' && inputs.use-restore-keys == 'true'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.path }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-${{ inputs.cache-key-suffix }}-
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
name: openclaw-codeql-actions-critical-security
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-extended
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
tags contain: security
|
||||
security-severity: /([7-9]|10)\.(\d)+/
|
||||
|
||||
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,56 +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:
|
||||
- extensions/bluebubbles/src
|
||||
- extensions/discord/src
|
||||
- extensions/feishu/src
|
||||
- extensions/googlechat/src
|
||||
- extensions/imessage/src
|
||||
- extensions/irc/src
|
||||
- extensions/line/src
|
||||
- extensions/matrix/src
|
||||
- extensions/mattermost/src
|
||||
- extensions/msteams/src
|
||||
- extensions/nextcloud-talk/src
|
||||
- extensions/nostr/src
|
||||
- extensions/qa-channel/src
|
||||
- extensions/qqbot/src
|
||||
- extensions/signal/src
|
||||
- extensions/slack/src
|
||||
- extensions/synology-chat/src
|
||||
- extensions/telegram/src
|
||||
- extensions/tlon/src
|
||||
- extensions/twitch/src
|
||||
- extensions/whatsapp/src
|
||||
- extensions/zalo/src
|
||||
- extensions/zalouser/src
|
||||
- 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,48 +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
|
||||
tags contain: security
|
||||
security-severity: /([7-9]|10)\.(\d)+/
|
||||
|
||||
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,55 +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
|
||||
tags contain: security
|
||||
security-severity: /([7-9]|10)\.(\d)+/
|
||||
|
||||
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,37 +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/method-scopes.ts
|
||||
- src/gateway/protocol
|
||||
- src/gateway/server-methods
|
||||
- src/gateway/server-methods.ts
|
||||
- src/gateway/server-methods-list.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*"
|
||||
18
.github/codeql/codeql-javascript-typescript.yml
vendored
Normal file
18
.github/codeql/codeql-javascript-typescript.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: openclaw-codeql-javascript-typescript
|
||||
|
||||
paths:
|
||||
- src
|
||||
- extensions
|
||||
- ui/src
|
||||
- skills
|
||||
|
||||
paths-ignore:
|
||||
- apps
|
||||
- dist
|
||||
- docs
|
||||
- "**/node_modules"
|
||||
- "**/coverage"
|
||||
- "**/*.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,56 +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
|
||||
tags contain: security
|
||||
security-severity: /([7-9]|10)\.(\d)+/
|
||||
|
||||
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,41 +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
|
||||
tags contain: security
|
||||
security-severity: /([7-9]|10)\.(\d)+/
|
||||
|
||||
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,44 +0,0 @@
|
||||
name: openclaw-codeql-plugin-sdk-reply-runtime-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/plugin-sdk/inbound-envelope.ts
|
||||
- src/plugin-sdk/inbound-reply-dispatch.ts
|
||||
- src/plugin-sdk/reply-*.ts
|
||||
- src/plugin-sdk/channel-reply-*.ts
|
||||
- src/plugin-sdk/delivery-queue-runtime.ts
|
||||
- src/plugin-sdk/outbound-runtime.ts
|
||||
- src/plugin-sdk/outbound-send-deps.ts
|
||||
- src/plugin-sdk/model-session-runtime.ts
|
||||
- src/plugin-sdk/session-*.ts
|
||||
- src/plugin-sdk/thread-bindings-runtime.ts
|
||||
- src/plugin-sdk/thread-bindings-session-runtime.ts
|
||||
- src/plugin-sdk/conversation-binding-runtime.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,87 +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
|
||||
tags contain: security
|
||||
security-severity: /([7-9]|10)\.(\d)+/
|
||||
|
||||
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,44 +0,0 @@
|
||||
name: openclaw-codeql-provider-runtime-boundary-critical-quality
|
||||
|
||||
disable-default-queries: true
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- include:
|
||||
problem.severity:
|
||||
- error
|
||||
- exclude:
|
||||
tags:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- src/model-catalog
|
||||
- src/plugins/provider-*.ts
|
||||
- src/plugins/providers*.ts
|
||||
- src/plugins/*provider*.ts
|
||||
- src/plugins/capability-provider-runtime.ts
|
||||
- src/plugins/compaction-provider.ts
|
||||
- src/plugins/memory-embedding-provider*.ts
|
||||
- src/plugins/memory-embedding-providers*.ts
|
||||
- src/plugins/migration-provider-runtime.ts
|
||||
- src/plugins/synthetic-auth.runtime.ts
|
||||
- src/plugins/web-fetch-providers*.ts
|
||||
- src/plugins/web-search-providers*.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,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*"
|
||||
33
.github/codex/prompts/docs-agent.md
vendored
33
.github/codex/prompts/docs-agent.md
vendored
@@ -1,33 +0,0 @@
|
||||
# OpenClaw Docs Agent
|
||||
|
||||
You are maintaining OpenClaw documentation after a main-branch commit.
|
||||
|
||||
Goal: inspect the code changes and existing documentation, then update existing docs only when they are stale, incomplete, or misleading.
|
||||
|
||||
Hard limits:
|
||||
|
||||
- Edit existing files only.
|
||||
- Do not create new docs pages, images, assets, scripts, code files, or workflow files.
|
||||
- Do not delete or rename files.
|
||||
- Do not change production code, tests, package metadata, generated baselines, lockfiles, or CI config.
|
||||
- Keep changes minimal and factual.
|
||||
- Use "plugin/plugins" in user-facing docs/UI/changelog; `extensions/` is only the internal workspace layout.
|
||||
- Do not add a changelog entry unless the docs update describes a user-facing behavior/API change from the triggering commit.
|
||||
|
||||
Allowed paths:
|
||||
|
||||
- `docs/**`
|
||||
- `README.md`
|
||||
- `CHANGELOG.md`
|
||||
|
||||
Required workflow:
|
||||
|
||||
1. Run `pnpm docs:list` if available and read relevant docs based on `read_when` hints.
|
||||
2. Inspect the triggering event via `$GITHUB_EVENT_PATH`, then review `$DOCS_AGENT_BASE_SHA..$DOCS_AGENT_HEAD_SHA` and its changed files. If either env var is missing, fall back to the event payload.
|
||||
3. Update stale existing documentation, if needed.
|
||||
4. Run `pnpm check:docs` if dependencies are available.
|
||||
5. Leave the worktree clean if no docs need changes.
|
||||
|
||||
If `pnpm docs:check-mdx` or `pnpm check:docs` reports MDX parse errors, fix only the syntax needed for the listed existing docs files. Preserve prose meaning, frontmatter, code fences, and links; do not broadly rewrite translated or source content while repairing parser failures.
|
||||
|
||||
When uncertain, prefer no edit and explain the uncertainty in the final message.
|
||||
25
.github/codex/prompts/docs-mdx-repair.md
vendored
25
.github/codex/prompts/docs-mdx-repair.md
vendored
@@ -1,25 +0,0 @@
|
||||
# OpenClaw Docs MDX Repair Agent
|
||||
|
||||
You are repairing generated OpenClaw documentation after a fast MDX validation failure.
|
||||
|
||||
Goal: fix only the MDX syntax errors reported by the checker.
|
||||
|
||||
Hard limits:
|
||||
|
||||
- Edit only existing Markdown/MDX files under the locale path named by `LOCALE`.
|
||||
- Do not edit source English docs unless `LOCALE=en`.
|
||||
- Do not edit code, workflows, package metadata, generated sync metadata, translation memory, or assets.
|
||||
- Do not add, delete, or rename files.
|
||||
- Preserve the meaning of translated prose.
|
||||
- Preserve frontmatter, `x-i18n.source_hash`, links, code fences, JSX component names, and existing page structure.
|
||||
- Avoid broad formatting or retranslation.
|
||||
|
||||
Required workflow:
|
||||
|
||||
1. Read `.openclaw-sync/mdx/${LOCALE}.json` when it exists.
|
||||
2. Inspect only the listed files and nearby lines.
|
||||
3. Fix the minimal syntax issue, such as broken JSX attribute quoting, mismatched component closing tags, raw `<` text, raw HTML comments, or accidental top-level `import`/`export` text.
|
||||
4. Run `node source/scripts/check-docs-mdx.mjs "docs/${LOCALE}" --json-out ".openclaw-sync/mdx/${LOCALE}.json"`.
|
||||
5. Leave no changes outside `docs/${LOCALE}`.
|
||||
|
||||
When uncertain, prefer the smallest escaping fix: backticks for literal words, `<` for literal `<`, double quotes around JSX attribute values, and balanced component tags.
|
||||
44
.github/codex/prompts/test-performance-agent.md
vendored
44
.github/codex/prompts/test-performance-agent.md
vendored
@@ -1,44 +0,0 @@
|
||||
# OpenClaw Test Performance Agent
|
||||
|
||||
You are maintaining OpenClaw test performance after a trusted main-branch CI run.
|
||||
|
||||
Goal: inspect the full-suite test performance report, then make small, coverage-preserving improvements to slow tests when the fix is clear. If the baseline report shows failing tests and the fix is obvious, fix those too.
|
||||
|
||||
Inputs:
|
||||
|
||||
- Baseline grouped report: `.artifacts/test-perf/baseline-before.json`
|
||||
- Per-config Vitest JSON reports: `.artifacts/test-perf/baseline-before/vitest-json/`
|
||||
- Per-config logs: `.artifacts/test-perf/baseline-before/logs/`
|
||||
|
||||
Hard limits:
|
||||
|
||||
- Preserve test coverage and behavioral intent.
|
||||
- Do not delete, skip, weaken, or narrow test cases to make the suite faster.
|
||||
- Do not add `test.skip`, `it.skip`, `describe.skip`, `test.only`, `it.only`, or `describe.only`.
|
||||
- Do not update snapshots, generated baselines, inventories, ignore files, lockfiles, package metadata, CI workflows, or release metadata.
|
||||
- Do not add dependencies.
|
||||
- Do not create, delete, or rename files.
|
||||
- Do not do broad refactors or style-only rewrites.
|
||||
- Keep changes minimal and focused on the slow or failing tests you can justify from the report.
|
||||
- Prefer no edit when a performance improvement is speculative.
|
||||
- If `.artifacts/test-perf/baseline-before.json` has `"failed": true`, do not make performance-only edits. First inspect the failed config logs. Edit only when the test failure has an obvious, coverage-preserving fix. If no obvious failure fix exists, leave the worktree clean.
|
||||
|
||||
Good fixes:
|
||||
|
||||
- Replace broad partial module mocks, especially `importOriginal()` mocks, with narrow injected dependencies or local runtime seams.
|
||||
- Avoid importing heavy barrels in hot tests when a narrow module or helper covers the same behavior.
|
||||
- Add or adjust a production lazy/injection seam only when that is the narrowest way to preserve coverage while removing expensive imports or fixing an obvious mock/import failure.
|
||||
- Move expensive setup from per-test hooks to shared setup only when state isolation remains correct.
|
||||
- Reuse existing fixtures/builders instead of recreating expensive work per case.
|
||||
- Mock expensive runtime boundaries directly: filesystem crawls, package registries, provider SDKs, network/process launch, browser/runtime scanners.
|
||||
- Keep one integration smoke per boundary and test pure helpers directly, but only when the same behavior remains covered.
|
||||
|
||||
Required workflow:
|
||||
|
||||
1. Run `pnpm docs:list` if available, then read `docs/reference/test.md` and `docs/help/testing.md` sections about test performance.
|
||||
2. Inspect `.artifacts/test-perf/baseline-before.json`. If `failed` is true, inspect the failed config logs before looking at slow files.
|
||||
3. Pick at most a few low-risk files. When baseline failed, pick only files needed for the obvious failure fix; otherwise focus on the slowest files/configs. Explain the coverage-preserving reason in comments only if the code would otherwise be unclear.
|
||||
4. Run targeted tests for changed files where possible. Use `pnpm test <path>` and optionally `pnpm test:perf:imports <path>`.
|
||||
5. Leave the worktree clean if no safe improvement exists.
|
||||
|
||||
When uncertain, make no edit and explain the uncertainty in the final message.
|
||||
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/*
|
||||
79
.github/labeler.yml
vendored
79
.github/labeler.yml
vendored
@@ -3,18 +3,6 @@
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/bluebubbles/**"
|
||||
- "docs/channels/bluebubbles.md"
|
||||
"plugin: azure-speech":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/azure-speech/**"
|
||||
- "docs/providers/azure-speech.md"
|
||||
- "docs/tools/tts.md"
|
||||
"plugin: file-transfer":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/file-transfer/**"
|
||||
- "docs/nodes/index.md"
|
||||
- "docs/plugins/sdk-runtime.md"
|
||||
"channel: discord":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -36,27 +24,6 @@
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/googlechat/**"
|
||||
- "docs/channels/googlechat.md"
|
||||
"plugin: google-meet":
|
||||
- changed-files:
|
||||
- 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:
|
||||
- "extensions/bonjour/**"
|
||||
- "docs/gateway/bonjour.md"
|
||||
"channel: imessage":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -118,11 +85,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:
|
||||
@@ -244,11 +206,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:
|
||||
@@ -258,10 +217,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:
|
||||
@@ -314,20 +269,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:
|
||||
@@ -352,11 +297,6 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/huggingface/**"
|
||||
"extensions: inworld":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/inworld/**"
|
||||
- "docs/providers/inworld.md"
|
||||
"extensions: kilocode":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -365,11 +305,6 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/lmstudio/**"
|
||||
"extensions: litellm":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/litellm/**"
|
||||
- "docs/providers/litellm.md"
|
||||
"extensions: openai":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -406,11 +341,6 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/qianfan/**"
|
||||
"extensions: senseaudio":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/senseaudio/**"
|
||||
- "docs/providers/senseaudio.md"
|
||||
"extensions: synthetic":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -427,11 +357,6 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/together/**"
|
||||
"extensions: tts-local-cli":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/tts-local-cli/**"
|
||||
- "docs/tools/tts.md"
|
||||
"extensions: venice":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
@@ -452,7 +377,3 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/fal/**"
|
||||
"extensions: gradium":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/gradium/**"
|
||||
|
||||
BIN
.github/pr-assets/compaction-checkpoints/sessions-checkpoints-inline.png
vendored
Normal file
BIN
.github/pr-assets/compaction-checkpoints/sessions-checkpoints-inline.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
.github/pr-assets/compaction-checkpoints/sessions-overview-inline.png
vendored
Normal file
BIN
.github/pr-assets/compaction-checkpoints/sessions-overview-inline.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
507
.github/workflows/auto-response.yml
vendored
507
.github/workflows/auto-response.yml
vendored
@@ -5,8 +5,8 @@ on:
|
||||
types: [opened, edited, labeled]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned label automation; trusted base checkout only, no untrusted PR code execution
|
||||
types: [opened, edited, synchronize, reopened, labeled]
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] maintainer-owned label automation; no untrusted checkout or code execution
|
||||
types: [labeled]
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -20,15 +20,10 @@ permissions: {}
|
||||
jobs:
|
||||
auto-response:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
persist-credentials: false
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
continue-on-error: true
|
||||
@@ -41,15 +36,499 @@ jobs:
|
||||
with:
|
||||
app-id: "2971289"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
|
||||
- name: Run Barnacle auto-response
|
||||
- name: Handle labeled items
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
script: |
|
||||
const { pathToFileURL } = require("node:url");
|
||||
const moduleUrl = pathToFileURL(
|
||||
`${process.env.GITHUB_WORKSPACE}/scripts/github/barnacle-auto-response.mjs`,
|
||||
);
|
||||
const { runBarnacleAutoResponse } = await import(moduleUrl.href);
|
||||
// Labels prefixed with "r:" are auto-response triggers.
|
||||
const activePrLimit = 10;
|
||||
const rules = [
|
||||
{
|
||||
label: "r: skill",
|
||||
close: true,
|
||||
message:
|
||||
"Thanks for the contribution! New skills should be published to [Clawhub](https://clawhub.ai) for everyone to use. We’re keeping the core lean on skills, so I’m closing this out.",
|
||||
},
|
||||
{
|
||||
label: "r: support",
|
||||
close: true,
|
||||
message:
|
||||
"Please use [our support server](https://discord.gg/clawd) and ask in #help or #users-helping-users to resolve this, or follow the stuck FAQ at https://docs.openclaw.ai/help/faq#im-stuck-whats-the-fastest-way-to-get-unstuck.",
|
||||
},
|
||||
{
|
||||
label: "r: no-ci-pr",
|
||||
close: true,
|
||||
message:
|
||||
"Please don't make PRs for test failures on main.\n\n" +
|
||||
"The team is aware of those and will handle them directly on the codebase, not only fixing the tests but also investigating what the root cause is. Having to sift through test-fix-PRs (including some that have been out of date for weeks...) on top of that doesn't help. There are already way too many PRs for humans to manage; please don't make the flood worse.\n\n" +
|
||||
"Thank you.",
|
||||
},
|
||||
{
|
||||
label: "r: too-many-prs",
|
||||
close: true,
|
||||
message:
|
||||
`Closing this PR because the author has more than ${activePrLimit} active PRs in this repo. ` +
|
||||
"Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit.",
|
||||
},
|
||||
{
|
||||
label: "r: testflight",
|
||||
close: true,
|
||||
commentTriggers: ["testflight"],
|
||||
message: "Not available, build from source.",
|
||||
},
|
||||
{
|
||||
label: "r: third-party-extension",
|
||||
close: true,
|
||||
message:
|
||||
"Please make this as a third-party plugin that you maintain yourself in your own repo. Docs: https://docs.openclaw.ai/plugin. Feel free to open a PR after to add it to our community plugins page: https://docs.openclaw.ai/plugins/community",
|
||||
},
|
||||
{
|
||||
label: "r: moltbook",
|
||||
close: true,
|
||||
lock: true,
|
||||
lockReason: "off-topic",
|
||||
commentTriggers: ["moltbook"],
|
||||
message:
|
||||
"OpenClaw is not affiliated with Moltbook, and issues related to Moltbook should not be submitted here.",
|
||||
},
|
||||
];
|
||||
|
||||
await runBarnacleAutoResponse({ github, context, core });
|
||||
const maintainerTeam = "maintainer";
|
||||
const pingWarningMessage =
|
||||
"Please don’t spam-ping multiple maintainers at once. Be patient, or join our community Discord for help: https://discord.gg/clawd";
|
||||
const mentionRegex = /@([A-Za-z0-9-]+)/g;
|
||||
const maintainerCache = new Map();
|
||||
const normalizeLogin = (login) => login.toLowerCase();
|
||||
const bugSubtypeLabelSpecs = {
|
||||
regression: {
|
||||
color: "D93F0B",
|
||||
description: "Behavior that previously worked and now fails",
|
||||
},
|
||||
"bug:crash": {
|
||||
color: "B60205",
|
||||
description: "Process/app exits unexpectedly or hangs",
|
||||
},
|
||||
"bug:behavior": {
|
||||
color: "D73A4A",
|
||||
description: "Incorrect behavior without a crash",
|
||||
},
|
||||
};
|
||||
const bugTypeToLabel = {
|
||||
"Regression (worked before, now fails)": "regression",
|
||||
"Crash (process/app exits or hangs)": "bug:crash",
|
||||
"Behavior bug (incorrect output/state without crash)": "bug:behavior",
|
||||
};
|
||||
const bugSubtypeLabels = Object.keys(bugSubtypeLabelSpecs);
|
||||
|
||||
const extractIssueFormValue = (body, field) => {
|
||||
if (!body) {
|
||||
return "";
|
||||
}
|
||||
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const regex = new RegExp(
|
||||
`(?:^|\\n)###\\s+${escapedField}\\s*\\n([\\s\\S]*?)(?=\\n###\\s+|$)`,
|
||||
"i",
|
||||
);
|
||||
const match = body.match(regex);
|
||||
if (!match) {
|
||||
return "";
|
||||
}
|
||||
for (const line of match[1].split("\n")) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const ensureLabelExists = async (name, color, description) => {
|
||||
try {
|
||||
await github.rest.issues.getLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error?.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
await github.rest.issues.createLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name,
|
||||
color,
|
||||
description,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const syncBugSubtypeLabel = async (issue, labelSet) => {
|
||||
if (!labelSet.has("bug")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedBugType = extractIssueFormValue(issue.body ?? "", "Bug type");
|
||||
const targetLabel = bugTypeToLabel[selectedBugType];
|
||||
if (!targetLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetSpec = bugSubtypeLabelSpecs[targetLabel];
|
||||
await ensureLabelExists(targetLabel, targetSpec.color, targetSpec.description);
|
||||
|
||||
for (const subtypeLabel of bugSubtypeLabels) {
|
||||
if (subtypeLabel === targetLabel) {
|
||||
continue;
|
||||
}
|
||||
if (!labelSet.has(subtypeLabel)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
name: subtypeLabel,
|
||||
});
|
||||
labelSet.delete(subtypeLabel);
|
||||
} catch (error) {
|
||||
if (error?.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!labelSet.has(targetLabel)) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: [targetLabel],
|
||||
});
|
||||
labelSet.add(targetLabel);
|
||||
}
|
||||
};
|
||||
|
||||
const isMaintainer = async (login) => {
|
||||
if (!login) {
|
||||
return false;
|
||||
}
|
||||
const normalized = normalizeLogin(login);
|
||||
if (maintainerCache.has(normalized)) {
|
||||
return maintainerCache.get(normalized);
|
||||
}
|
||||
let isMember = false;
|
||||
try {
|
||||
const membership = await github.rest.teams.getMembershipForUserInOrg({
|
||||
org: context.repo.owner,
|
||||
team_slug: maintainerTeam,
|
||||
username: normalized,
|
||||
});
|
||||
isMember = membership?.data?.state === "active";
|
||||
} catch (error) {
|
||||
if (error?.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
maintainerCache.set(normalized, isMember);
|
||||
return isMember;
|
||||
};
|
||||
|
||||
const countMaintainerMentions = async (body, authorLogin) => {
|
||||
if (!body) {
|
||||
return 0;
|
||||
}
|
||||
const normalizedAuthor = authorLogin ? normalizeLogin(authorLogin) : "";
|
||||
if (normalizedAuthor && (await isMaintainer(normalizedAuthor))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const haystack = body.toLowerCase();
|
||||
const teamMention = `@${context.repo.owner.toLowerCase()}/${maintainerTeam}`;
|
||||
if (haystack.includes(teamMention)) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
const mentions = new Set();
|
||||
for (const match of body.matchAll(mentionRegex)) {
|
||||
mentions.add(normalizeLogin(match[1]));
|
||||
}
|
||||
if (normalizedAuthor) {
|
||||
mentions.delete(normalizedAuthor);
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
for (const login of mentions) {
|
||||
if (await isMaintainer(login)) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
const triggerLabel = "trigger-response";
|
||||
const activePrLimitLabel = "r: too-many-prs";
|
||||
const activePrLimitOverrideLabel = "r: too-many-prs-override";
|
||||
const target = context.payload.issue ?? context.payload.pull_request;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labelSet = new Set(
|
||||
(target.labels ?? [])
|
||||
.map((label) => (typeof label === "string" ? label : label?.name))
|
||||
.filter((name) => typeof name === "string"),
|
||||
);
|
||||
|
||||
const issue = context.payload.issue;
|
||||
const pullRequest = context.payload.pull_request;
|
||||
const comment = context.payload.comment;
|
||||
if (comment) {
|
||||
const authorLogin = comment.user?.login ?? "";
|
||||
if (comment.user?.type === "Bot" || authorLogin.endsWith("[bot]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commentBody = comment.body ?? "";
|
||||
const responses = [];
|
||||
const mentionCount = await countMaintainerMentions(commentBody, authorLogin);
|
||||
if (mentionCount >= 3) {
|
||||
responses.push(pingWarningMessage);
|
||||
}
|
||||
|
||||
const commentHaystack = commentBody.toLowerCase();
|
||||
const commentRule = rules.find((item) =>
|
||||
(item.commentTriggers ?? []).some((trigger) =>
|
||||
commentHaystack.includes(trigger),
|
||||
),
|
||||
);
|
||||
if (commentRule) {
|
||||
responses.push(commentRule.message);
|
||||
}
|
||||
|
||||
if (responses.length > 0) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: target.number,
|
||||
body: responses.join("\n\n"),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (issue) {
|
||||
const action = context.payload.action;
|
||||
if (action === "opened" || action === "edited") {
|
||||
const issueText = `${issue.title ?? ""}\n${issue.body ?? ""}`.trim();
|
||||
const authorLogin = issue.user?.login ?? "";
|
||||
const mentionCount = await countMaintainerMentions(
|
||||
issueText,
|
||||
authorLogin,
|
||||
);
|
||||
if (mentionCount >= 3) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: pingWarningMessage,
|
||||
});
|
||||
}
|
||||
|
||||
await syncBugSubtypeLabel(issue, labelSet);
|
||||
}
|
||||
}
|
||||
|
||||
const hasTriggerLabel = labelSet.has(triggerLabel);
|
||||
if (hasTriggerLabel) {
|
||||
labelSet.delete(triggerLabel);
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: target.number,
|
||||
name: triggerLabel,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error?.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isLabelEvent = context.payload.action === "labeled";
|
||||
if (!hasTriggerLabel && !isLabelEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (issue) {
|
||||
const title = issue.title ?? "";
|
||||
const body = issue.body ?? "";
|
||||
const haystack = `${title}\n${body}`.toLowerCase();
|
||||
const hasMoltbookLabel = labelSet.has("r: moltbook");
|
||||
const hasTestflightLabel = labelSet.has("r: testflight");
|
||||
const hasSecurityLabel = labelSet.has("security");
|
||||
if (title.toLowerCase().includes("security") && !hasSecurityLabel) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ["security"],
|
||||
});
|
||||
labelSet.add("security");
|
||||
}
|
||||
if (title.toLowerCase().includes("testflight") && !hasTestflightLabel) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ["r: testflight"],
|
||||
});
|
||||
labelSet.add("r: testflight");
|
||||
}
|
||||
if (haystack.includes("moltbook") && !hasMoltbookLabel) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: ["r: moltbook"],
|
||||
});
|
||||
labelSet.add("r: moltbook");
|
||||
}
|
||||
}
|
||||
|
||||
const invalidLabel = "invalid";
|
||||
const spamLabel = "r: spam";
|
||||
const dirtyLabel = "dirty";
|
||||
const badBarnacleLabel = "bad-barnacle";
|
||||
const noisyPrMessage =
|
||||
"Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.";
|
||||
|
||||
if (pullRequest) {
|
||||
if (labelSet.has(badBarnacleLabel)) {
|
||||
core.info(`Skipping PR auto-response checks for #${pullRequest.number} because ${badBarnacleLabel} is present.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (labelSet.has(dirtyLabel)) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
body: noisyPrMessage,
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
state: "closed",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const labelCount = labelSet.size;
|
||||
if (labelCount > 20) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
body: noisyPrMessage,
|
||||
});
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
state: "closed",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (labelSet.has(spamLabel)) {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
state: "closed",
|
||||
});
|
||||
await github.rest.issues.lock({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
lock_reason: "spam",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (labelSet.has(invalidLabel)) {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
state: "closed",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (issue && labelSet.has(spamLabel)) {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned",
|
||||
});
|
||||
await github.rest.issues.lock({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
lock_reason: "spam",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (issue && labelSet.has(invalidLabel)) {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (pullRequest && labelSet.has(activePrLimitOverrideLabel)) {
|
||||
labelSet.delete(activePrLimitLabel);
|
||||
}
|
||||
|
||||
const rule = rules.find((item) => labelSet.has(item.label));
|
||||
if (!rule) {
|
||||
return;
|
||||
}
|
||||
|
||||
const issueNumber = target.number;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
body: rule.message,
|
||||
});
|
||||
|
||||
if (rule.close) {
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
state: "closed",
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.lock) {
|
||||
await github.rest.issues.lock({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
lock_reason: rule.lockReason ?? "resolved",
|
||||
});
|
||||
}
|
||||
|
||||
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"
|
||||
39
.github/workflows/ci-check-testbox.yml
vendored
39
.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
|
||||
@@ -62,18 +62,18 @@ jobs:
|
||||
|
||||
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"
|
||||
echo "checkout attempt ${attempt}/2 succeeded"
|
||||
}
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
for attempt in 1 2; do
|
||||
if checkout_attempt "$attempt"; then
|
||||
exit 0
|
||||
fi
|
||||
echo "checkout attempt ${attempt}/5 failed"
|
||||
echo "checkout attempt ${attempt}/2 failed"
|
||||
sleep $((attempt * 5))
|
||||
done
|
||||
|
||||
echo "checkout failed after 5 attempts" >&2
|
||||
echo "checkout failed after 2 attempts" >&2
|
||||
exit 1
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
@@ -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"
|
||||
|
||||
1172
.github/workflows/ci.yml
vendored
1172
.github/workflows/ci.yml
vendored
File diff suppressed because it is too large
Load Diff
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"
|
||||
619
.github/workflows/codeql-critical-quality.yml
vendored
619
.github/workflows/codeql-critical-quality.yml
vendored
@@ -1,619 +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
|
||||
- agent-runtime-boundary
|
||||
- config-boundary
|
||||
- core-auth-secrets
|
||||
- channel-runtime-boundary
|
||||
- gateway-runtime-boundary
|
||||
- memory-runtime-boundary
|
||||
- mcp-process-runtime-boundary
|
||||
- plugin-boundary
|
||||
- plugin-sdk-package-contract
|
||||
- plugin-sdk-reply-runtime
|
||||
- provider-runtime-boundary
|
||||
- session-diagnostics-boundary
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- ".github/codeql/**"
|
||||
- ".github/workflows/codeql-critical-quality.yml"
|
||||
- "packages/plugin-package-contract/**"
|
||||
- "packages/plugin-sdk/**"
|
||||
- "packages/memory-host-sdk/**"
|
||||
- "src/config/**"
|
||||
- "extensions/bluebubbles/src/**"
|
||||
- "extensions/discord/src/**"
|
||||
- "extensions/feishu/src/**"
|
||||
- "extensions/googlechat/src/**"
|
||||
- "extensions/imessage/src/**"
|
||||
- "extensions/irc/src/**"
|
||||
- "extensions/line/src/**"
|
||||
- "extensions/matrix/src/**"
|
||||
- "extensions/mattermost/src/**"
|
||||
- "extensions/msteams/src/**"
|
||||
- "extensions/nextcloud-talk/src/**"
|
||||
- "extensions/nostr/src/**"
|
||||
- "extensions/qa-channel/src/**"
|
||||
- "extensions/qqbot/src/**"
|
||||
- "extensions/signal/src/**"
|
||||
- "extensions/slack/src/**"
|
||||
- "extensions/synology-chat/src/**"
|
||||
- "extensions/telegram/src/**"
|
||||
- "extensions/tlon/src/**"
|
||||
- "extensions/twitch/src/**"
|
||||
- "extensions/whatsapp/src/**"
|
||||
- "extensions/zalo/src/**"
|
||||
- "extensions/zalouser/src/**"
|
||||
- "src/agents/*auth*.ts"
|
||||
- "src/agents/**/*auth*.ts"
|
||||
- "src/agents/auth-health*.ts"
|
||||
- "src/agents/auth-profiles"
|
||||
- "src/agents/auth-profiles/**"
|
||||
- "src/agents/bash-tools.exec-host-shared.ts"
|
||||
- "src/agents/sandbox"
|
||||
- "src/agents/sandbox/**"
|
||||
- "src/agents/sandbox.ts"
|
||||
- "src/agents/sandbox-*.ts"
|
||||
- "src/acp/control-plane/**"
|
||||
- "src/agents/cli-runner/**"
|
||||
- "src/agents/command/**"
|
||||
- "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"
|
||||
- "src/channels/**"
|
||||
- "src/auto-reply/reply/post-compaction-context.ts"
|
||||
- "src/auto-reply/reply/queue/**"
|
||||
- "src/auto-reply/reply/startup-context.ts"
|
||||
- "src/commands/doctor-cron-dreaming-payload-migration.ts"
|
||||
- "src/commands/doctor-memory-search.ts"
|
||||
- "src/commands/doctor-session-*.ts"
|
||||
- "src/commands/session-store-targets.ts"
|
||||
- "src/commands/sessions*.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/gateway/server-startup-memory.ts"
|
||||
- "src/gateway/method-scopes.ts"
|
||||
- "src/gateway/protocol/**"
|
||||
- "src/gateway/server-methods/**"
|
||||
- "src/gateway/server-methods.ts"
|
||||
- "src/gateway/server-methods-list.ts"
|
||||
- "src/infra/diagnostic-*.ts"
|
||||
- "src/infra/diagnostics-timeline.ts"
|
||||
- "src/infra/outbound/**"
|
||||
- "src/infra/secret-file*.ts"
|
||||
- "src/infra/session-delivery-queue*.ts"
|
||||
- "src/logging/diagnostic*.ts"
|
||||
- "src/memory/**"
|
||||
- "src/memory-host-sdk/**"
|
||||
- "src/mcp/**"
|
||||
- "src/model-catalog/**"
|
||||
- "src/plugin-sdk/**"
|
||||
- "src/plugins/**"
|
||||
- "src/process/**"
|
||||
- "src/secrets/**"
|
||||
- "src/security/**"
|
||||
schedule:
|
||||
- cron: "30 6 * * *"
|
||||
|
||||
concurrency:
|
||||
group: codeql-critical-quality-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.event_name == 'pull_request' && github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
quality-shards:
|
||||
name: Select Critical Quality shards
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
agent: ${{ steps.detect.outputs.agent }}
|
||||
channel: ${{ steps.detect.outputs.channel }}
|
||||
config: ${{ steps.detect.outputs.config }}
|
||||
core_auth_secrets: ${{ steps.detect.outputs.core_auth_secrets }}
|
||||
gateway: ${{ steps.detect.outputs.gateway }}
|
||||
memory: ${{ steps.detect.outputs.memory }}
|
||||
mcp_process: ${{ steps.detect.outputs.mcp_process }}
|
||||
plugin: ${{ steps.detect.outputs.plugin }}
|
||||
plugin_sdk_package: ${{ steps.detect.outputs.plugin_sdk_package }}
|
||||
plugin_sdk_reply: ${{ steps.detect.outputs.plugin_sdk_reply }}
|
||||
provider: ${{ steps.detect.outputs.provider }}
|
||||
session_diagnostics: ${{ steps.detect.outputs.session_diagnostics }}
|
||||
steps:
|
||||
- name: Detect PR shard paths
|
||||
id: detect
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
agent=false
|
||||
channel=false
|
||||
config=false
|
||||
core_auth_secrets=false
|
||||
gateway=false
|
||||
memory=false
|
||||
mcp_process=false
|
||||
plugin=false
|
||||
plugin_sdk_package=false
|
||||
plugin_sdk_reply=false
|
||||
provider=false
|
||||
session_diagnostics=false
|
||||
|
||||
if [[ "${EVENT_NAME}" != "pull_request" ]]; then
|
||||
agent=true
|
||||
channel=true
|
||||
config=true
|
||||
core_auth_secrets=true
|
||||
gateway=true
|
||||
memory=true
|
||||
mcp_process=true
|
||||
plugin=true
|
||||
plugin_sdk_package=true
|
||||
plugin_sdk_reply=true
|
||||
provider=true
|
||||
session_diagnostics=true
|
||||
else
|
||||
while IFS= read -r file; do
|
||||
case "${file}" in
|
||||
.github/codeql/*|.github/workflows/codeql-critical-quality.yml)
|
||||
agent=true
|
||||
channel=true
|
||||
config=true
|
||||
core_auth_secrets=true
|
||||
gateway=true
|
||||
memory=true
|
||||
mcp_process=true
|
||||
plugin=true
|
||||
plugin_sdk_package=true
|
||||
plugin_sdk_reply=true
|
||||
provider=true
|
||||
session_diagnostics=true
|
||||
;;
|
||||
src/acp/control-plane/*|src/agents/cli-runner/*|src/agents/command/*|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)
|
||||
agent=true
|
||||
;;
|
||||
src/auto-reply/reply/post-compaction-context.ts|src/auto-reply/reply/queue/*|src/auto-reply/reply/startup-context.ts|src/commands/doctor-session-*.ts|src/commands/session-store-targets.ts|src/commands/sessions*.ts|src/infra/diagnostic-*.ts|src/infra/diagnostics-timeline.ts|src/infra/session-delivery-queue*.ts|src/logging/diagnostic*.ts)
|
||||
session_diagnostics=true
|
||||
;;
|
||||
extensions/bluebubbles/src/*|extensions/discord/src/*|extensions/feishu/src/*|extensions/googlechat/src/*|extensions/imessage/src/*|extensions/irc/src/*|extensions/line/src/*|extensions/matrix/src/*|extensions/mattermost/src/*|extensions/msteams/src/*|extensions/nextcloud-talk/src/*|extensions/nostr/src/*|extensions/qa-channel/src/*|extensions/qqbot/src/*|extensions/signal/src/*|extensions/slack/src/*|extensions/synology-chat/src/*|extensions/telegram/src/*|extensions/tlon/src/*|extensions/twitch/src/*|extensions/whatsapp/src/*|extensions/zalo/src/*|extensions/zalouser/src/*|src/channels/*)
|
||||
channel=true
|
||||
;;
|
||||
src/config/*)
|
||||
config=true
|
||||
;;
|
||||
src/gateway/protocol/*secret*.ts|src/gateway/server-methods/secrets*.ts)
|
||||
core_auth_secrets=true
|
||||
gateway=true
|
||||
;;
|
||||
src/agents/*auth*.ts|src/agents/auth-health*.ts|src/agents/auth-profiles|src/agents/auth-profiles/*|src/agents/bash-tools.exec-host-shared.ts|src/agents/sandbox|src/agents/sandbox.ts|src/agents/sandbox-*.ts|src/agents/sandbox/*|src/cron/service/jobs.ts|src/cron/stagger.ts|src/gateway/*auth*.ts|src/gateway/*secret*.ts|src/gateway/resolve-configured-secret-input-string*.ts|src/gateway/security-path*.ts|src/infra/secret-file*.ts|src/secrets/*|src/security/*)
|
||||
core_auth_secrets=true
|
||||
;;
|
||||
src/gateway/method-scopes.ts|src/gateway/protocol/*|src/gateway/server-methods/*|src/gateway/server-methods.ts|src/gateway/server-methods-list.ts)
|
||||
gateway=true
|
||||
;;
|
||||
packages/memory-host-sdk/*|src/commands/doctor-cron-dreaming-payload-migration.ts|src/commands/doctor-memory-search.ts|src/gateway/server-startup-memory.ts|src/memory/*|src/memory-host-sdk/*)
|
||||
memory=true
|
||||
;;
|
||||
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)
|
||||
mcp_process=true
|
||||
session_diagnostics=true
|
||||
;;
|
||||
src/infra/outbound/*|src/mcp/*|src/process/*)
|
||||
mcp_process=true
|
||||
;;
|
||||
src/plugin-sdk/inbound-envelope.ts|src/plugin-sdk/inbound-reply-dispatch.ts|src/plugin-sdk/reply-*.ts|src/plugin-sdk/channel-reply-*.ts|src/plugin-sdk/delivery-queue-runtime.ts|src/plugin-sdk/outbound-runtime.ts|src/plugin-sdk/outbound-send-deps.ts|src/plugin-sdk/model-session-runtime.ts|src/plugin-sdk/session-*.ts|src/plugin-sdk/thread-bindings-runtime.ts|src/plugin-sdk/thread-bindings-session-runtime.ts|src/plugin-sdk/conversation-binding-runtime.ts)
|
||||
plugin=true
|
||||
plugin_sdk_package=true
|
||||
plugin_sdk_reply=true
|
||||
;;
|
||||
src/plugin-sdk/memory-*.ts|src/plugin-sdk/memory-core-host-*.ts)
|
||||
memory=true
|
||||
plugin=true
|
||||
plugin_sdk_package=true
|
||||
;;
|
||||
src/plugin-sdk/*)
|
||||
plugin=true
|
||||
plugin_sdk_package=true
|
||||
;;
|
||||
src/plugins/provider-contract-public-artifacts.ts|src/plugins/provider-public-artifacts.ts|src/plugins/web-provider-public-artifacts*.ts)
|
||||
plugin=true
|
||||
provider=true
|
||||
;;
|
||||
src/plugins/memory-embedding-provider*.ts|src/plugins/memory-embedding-providers*.ts)
|
||||
memory=true
|
||||
provider=true
|
||||
;;
|
||||
src/plugins/memory-*.ts)
|
||||
memory=true
|
||||
;;
|
||||
src/model-catalog/*|src/plugins/*provider*.ts|src/plugins/capability-provider-runtime.ts|src/plugins/compaction-provider.ts|src/plugins/memory-embedding-provider*.ts|src/plugins/memory-embedding-providers*.ts|src/plugins/migration-provider-runtime.ts|src/plugins/synthetic-auth.runtime.ts|src/plugins/web-fetch-providers*.ts|src/plugins/web-search-providers*.ts)
|
||||
provider=true
|
||||
;;
|
||||
src/plugins/activation-planner.ts|src/plugins/api-builder.ts|src/plugins/bundled-*.ts|src/plugins/captured-registration.ts|src/plugins/config-*.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/public-surface*.ts|src/plugins/registry.ts|src/plugins/registry-types.ts|src/plugins/runtime|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)
|
||||
plugin=true
|
||||
;;
|
||||
packages/plugin-package-contract/*|packages/plugin-sdk/*)
|
||||
plugin_sdk_package=true
|
||||
;;
|
||||
esac
|
||||
done < <(gh api --paginate "repos/${REPOSITORY}/pulls/${PR_NUMBER}/files" --jq '.[].filename')
|
||||
fi
|
||||
|
||||
{
|
||||
echo "agent=${agent}"
|
||||
echo "channel=${channel}"
|
||||
echo "config=${config}"
|
||||
echo "core_auth_secrets=${core_auth_secrets}"
|
||||
echo "gateway=${gateway}"
|
||||
echo "memory=${memory}"
|
||||
echo "mcp_process=${mcp_process}"
|
||||
echo "plugin=${plugin}"
|
||||
echo "plugin_sdk_package=${plugin_sdk_package}"
|
||||
echo "plugin_sdk_reply=${plugin_sdk_reply}"
|
||||
echo "provider=${provider}"
|
||||
echo "session_diagnostics=${session_diagnostics}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
core-auth-secrets:
|
||||
name: Critical Quality (core-auth-secrets)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.core_auth_secrets == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'core-auth-secrets') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.config == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'config-boundary') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.gateway == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'gateway-runtime-boundary') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.channel == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'channel-runtime-boundary') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.agent == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'agent-runtime-boundary') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.mcp_process == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'mcp-process-runtime-boundary') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.memory == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'memory-runtime-boundary') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.session_diagnostics == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'session-diagnostics-boundary') }}
|
||||
runs-on: 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"
|
||||
|
||||
plugin-sdk-reply-runtime:
|
||||
name: Critical Quality (plugin-sdk-reply-runtime)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.plugin_sdk_reply == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'plugin-sdk-reply-runtime') }}
|
||||
runs-on: 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-reply-runtime-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/plugin-sdk-reply-runtime"
|
||||
|
||||
provider-runtime-boundary:
|
||||
name: Critical Quality (provider-runtime-boundary)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.provider == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'provider-runtime-boundary') }}
|
||||
runs-on: 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-provider-runtime-boundary-critical-quality.yml
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
|
||||
with:
|
||||
category: "/codeql-critical-quality/provider-runtime-boundary"
|
||||
|
||||
ui-control-plane:
|
||||
name: Critical Quality (ui-control-plane)
|
||||
if: ${{ github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || inputs.profile == 'all') }}
|
||||
runs-on: 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 != 'pull_request' && (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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.plugin == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'plugin-boundary') }}
|
||||
runs-on: 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)
|
||||
needs: quality-shards
|
||||
if: ${{ needs.quality-shards.outputs.plugin_sdk_package == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'plugin-sdk-package-contract') }}
|
||||
runs-on: 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"
|
||||
154
.github/workflows/codeql.yml
vendored
154
.github/workflows/codeql.yml
vendored
@@ -2,28 +2,11 @@ name: CodeQL
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
profile:
|
||||
description: CodeQL security profile to run
|
||||
required: false
|
||||
default: all
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- security
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- ".github/actions/**"
|
||||
- ".github/codeql/**"
|
||||
- ".github/workflows/**"
|
||||
- "packages/**"
|
||||
- "src/**"
|
||||
schedule:
|
||||
- cron: "0 6 * * *"
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.event_name == 'pull_request' && github.event.pull_request.number || github.sha }}
|
||||
group: codeql-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
@@ -35,58 +18,121 @@ permissions:
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
security-high:
|
||||
name: Security High (${{ matrix.category }})
|
||||
if: ${{ (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'security') }}
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
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
|
||||
runs_on: blacksmith-16vcpu-ubuntu-2404
|
||||
needs_node: true
|
||||
needs_python: false
|
||||
needs_java: false
|
||||
needs_swift_tools: false
|
||||
needs_manual_build: false
|
||||
needs_autobuild: false
|
||||
config_file: ./.github/codeql/codeql-javascript-typescript.yml
|
||||
- language: actions
|
||||
category: actions
|
||||
runs_on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout_minutes: 10
|
||||
config_file: ./.github/codeql/codeql-actions-critical-security.yml
|
||||
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
|
||||
config_file: ""
|
||||
- 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
|
||||
config_file: ""
|
||||
- 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
|
||||
config_file: ""
|
||||
- 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
|
||||
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-security-high/${{ matrix.category }}"
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
@@ -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"]'
|
||||
|
||||
if [ "$EVENT_NAME" != "push" ]; then
|
||||
echo "has_locales=true" >> "$GITHUB_OUTPUT"
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENCLAW_DOCS_I18N_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
OPENCLAW_CONTROL_UI_I18N_MODEL: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
|
||||
OPENCLAW_CONTROL_UI_I18N_MODEL: gpt-5.4
|
||||
OPENCLAW_CONTROL_UI_I18N_THINKING: low
|
||||
LOCALE: ${{ matrix.locale }}
|
||||
run: node --import tsx scripts/control-ui-i18n.ts sync --locale "${LOCALE}" --write
|
||||
|
||||
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}"
|
||||
|
||||
251
.github/workflows/docs-agent.yml
vendored
251
.github/workflows/docs-agent.yml
vendored
@@ -1,251 +0,0 @@
|
||||
name: Docs Agent
|
||||
|
||||
on:
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers] main-only docs repair after trusted CI; job gates repository, event, branch, actor, conclusion, exact current main SHA, and hourly cadence before using write token
|
||||
workflows:
|
||||
- CI
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: docs-agent-main
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
update-docs:
|
||||
if: >
|
||||
github.repository == 'openclaw/openclaw' &&
|
||||
github.actor != 'github-actions[bot]' &&
|
||||
(github.event_name != 'workflow_run' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_branch == 'main' &&
|
||||
github.event.workflow_run.actor.login != 'github-actions[bot]'))
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Gate trusted main activity and hourly cadence
|
||||
id: gate
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$EVENT_NAME" != "workflow_run" ]; then
|
||||
head_sha="$(git rev-parse HEAD)"
|
||||
review_base="$(git rev-parse "${head_sha}^" 2>/dev/null || printf '%s' "$head_sha")"
|
||||
{
|
||||
echo "run_agent=true"
|
||||
echo "base_sha=${head_sha}"
|
||||
echo "review_base_sha=${review_base}"
|
||||
echo "review_head_sha=${head_sha}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if git fetch --no-tags origin main; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" = "5" ]; then
|
||||
echo "Failed to fetch main after retries." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Fetch attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
done
|
||||
remote_main="$(git rev-parse origin/main)"
|
||||
if [ "$remote_main" != "$WORKFLOW_HEAD_SHA" ]; then
|
||||
echo "CI run is superseded by ${remote_main}; skipping docs agent for ${WORKFLOW_HEAD_SHA}."
|
||||
echo "run_agent=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
runs_json="$RUNNER_TEMP/docs-agent-runs.json"
|
||||
gh api --method GET "repos/${GITHUB_REPOSITORY}/actions/workflows/docs-agent.yml/runs" \
|
||||
-f branch=main \
|
||||
-f event=workflow_run \
|
||||
-f per_page=100 > "$runs_json"
|
||||
|
||||
one_hour_ago="$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)"
|
||||
recent_runs="$(
|
||||
jq -r \
|
||||
--argjson current_run_id "$GITHUB_RUN_ID" \
|
||||
--arg one_hour_ago "$one_hour_ago" \
|
||||
'.workflow_runs[]
|
||||
| select(.database_id != $current_run_id)
|
||||
| select(.created_at >= $one_hour_ago)
|
||||
| select(.status != "cancelled")
|
||||
| select((.conclusion // "") != "skipped")
|
||||
| [.database_id, .status, (.conclusion // ""), .created_at, .head_sha]
|
||||
| @tsv' "$runs_json"
|
||||
)"
|
||||
|
||||
if [ -n "$recent_runs" ]; then
|
||||
echo "Docs agent already ran or is running within the last hour; skipping."
|
||||
printf '%s\n' "$recent_runs"
|
||||
echo "run_agent=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
review_base="$(
|
||||
jq -r \
|
||||
--argjson current_run_id "$GITHUB_RUN_ID" \
|
||||
--arg remote_main "$remote_main" \
|
||||
'.workflow_runs[]
|
||||
| select(.database_id != $current_run_id)
|
||||
| select(.status != "cancelled")
|
||||
| select((.conclusion // "") != "skipped")
|
||||
| .head_sha
|
||||
| select(. != null and . != "")
|
||||
| select(. != $remote_main)
|
||||
' "$runs_json" | head -n 1
|
||||
)"
|
||||
if [ -z "$review_base" ] || ! git cat-file -e "${review_base}^{commit}" 2>/dev/null; then
|
||||
review_base="$(git rev-parse "${remote_main}^" 2>/dev/null || printf '%s' "$remote_main")"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "run_agent=true"
|
||||
echo "base_sha=${remote_main}"
|
||||
echo "review_base_sha=${review_base}"
|
||||
echo "review_head_sha=${remote_main}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Node environment
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Ensure docs agent key exists
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENCLAW_DOCS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${OPENAI_API_KEY:-}" ]; then
|
||||
echo "Missing OPENCLAW_DOCS_AGENT_OPENAI_API_KEY or OPENAI_API_KEY secret." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run Codex docs agent
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
|
||||
env:
|
||||
DOCS_AGENT_BASE_SHA: ${{ steps.gate.outputs.review_base_sha }}
|
||||
DOCS_AGENT_HEAD_SHA: ${{ steps.gate.outputs.review_head_sha }}
|
||||
with:
|
||||
openai-api-key: ${{ secrets.OPENCLAW_DOCS_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
||||
prompt-file: .github/codex/prompts/docs-agent.md
|
||||
model: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
|
||||
effort: medium
|
||||
sandbox: workspace-write
|
||||
safety-strategy: drop-sudo
|
||||
codex-args: '["--full-auto"]'
|
||||
|
||||
- name: Enforce existing-docs-only patch
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
untracked="$(git ls-files --others --exclude-standard)"
|
||||
if [ -n "$untracked" ]; then
|
||||
echo "Docs agent created untracked files; forbidden:"
|
||||
printf '%s\n' "$untracked"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
added_or_deleted="$(git diff --name-status --diff-filter=AD)"
|
||||
if [ -n "$added_or_deleted" ]; then
|
||||
echo "Docs agent added or deleted tracked files; forbidden:"
|
||||
printf '%s\n' "$added_or_deleted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bad_paths="$(
|
||||
git diff --name-only | while IFS= read -r path; do
|
||||
case "$path" in
|
||||
docs/*|README.md|CHANGELOG.md) ;;
|
||||
*) printf '%s\n' "$path" ;;
|
||||
esac
|
||||
done
|
||||
)"
|
||||
if [ -n "$bad_paths" ]; then
|
||||
echo "Docs agent touched non-doc paths; forbidden:"
|
||||
printf '%s\n' "$bad_paths"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- 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
|
||||
set -euo pipefail
|
||||
export PATH="${NODE_BIN}:${PATH}"
|
||||
echo "${NODE_BIN}" >> "$GITHUB_PATH"
|
||||
node -v
|
||||
corepack enable
|
||||
pnpm -v
|
||||
|
||||
- name: Check docs
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
run: pnpm check:docs
|
||||
|
||||
- name: Commit docs updates
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
env:
|
||||
BASE_SHA: ${{ steps.gate.outputs.base_sha }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
TARGET_BRANCH: main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "No docs changes."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "openclaw-docs-agent[bot]"
|
||||
git config user.email "openclaw-docs-agent[bot]@users.noreply.github.com"
|
||||
git add docs README.md CHANGELOG.md
|
||||
git commit --no-verify -m "docs: refresh documentation"
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if ! git fetch --no-tags origin "${TARGET_BRANCH}"; then
|
||||
echo "Fetch attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
continue
|
||||
fi
|
||||
if git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:"${TARGET_BRANCH}"; then
|
||||
exit 0
|
||||
fi
|
||||
remote_main="$(git rev-parse "origin/${TARGET_BRANCH}")"
|
||||
if [ "$remote_main" != "$BASE_SHA" ]; then
|
||||
echo "main advanced from ${BASE_SHA} to ${remote_main}; skipping stale docs update."
|
||||
exit 0
|
||||
fi
|
||||
echo "Docs update attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
done
|
||||
|
||||
echo "Failed to push docs updates after retries." >&2
|
||||
exit 1
|
||||
56
.github/workflows/docs-sync-publish.yml
vendored
56
.github/workflows/docs-sync-publish.yml
vendored
@@ -32,19 +32,9 @@ jobs:
|
||||
OPENCLAW_DOCS_SYNC_TOKEN: ${{ secrets.OPENCLAW_DOCS_SYNC_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for attempt in 1 2 3 4 5; do
|
||||
rm -rf publish
|
||||
if git clone \
|
||||
"https://x-access-token:${OPENCLAW_DOCS_SYNC_TOKEN}@github.com/openclaw/docs.git" \
|
||||
publish; then
|
||||
exit 0
|
||||
fi
|
||||
echo "Clone attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
done
|
||||
|
||||
echo "Failed to clone publish repo after retries." >&2
|
||||
exit 1
|
||||
git clone \
|
||||
"https://x-access-token:${OPENCLAW_DOCS_SYNC_TOKEN}@github.com/openclaw/docs.git" \
|
||||
publish
|
||||
|
||||
- name: Sync docs into publish repo
|
||||
run: |
|
||||
@@ -53,56 +43,26 @@ jobs:
|
||||
--source-repo "$GITHUB_REPOSITORY" \
|
||||
--source-sha "$GITHUB_SHA"
|
||||
|
||||
- name: Install docs MDX checker dependency
|
||||
run: npm install --no-save --package-lock=false @mdx-js/mdx@3.1.1
|
||||
|
||||
- name: Check publish docs MDX
|
||||
run: node "$GITHUB_WORKSPACE/publish/.openclaw-sync/check-docs-mdx.mjs" "$GITHUB_WORKSPACE/publish/docs"
|
||||
|
||||
- name: Commit publish repo sync
|
||||
working-directory: publish
|
||||
run: |
|
||||
set -euo pipefail
|
||||
remote_source_sha() {
|
||||
git show refs/remotes/origin/main:.openclaw-sync/source.json 2>/dev/null \
|
||||
| node -e 'const fs = require("node:fs"); try { const data = JSON.parse(fs.readFileSync(0, "utf8")); if (data.sha) process.stdout.write(data.sha); } catch {}' \
|
||||
|| true
|
||||
}
|
||||
|
||||
skip_stale_source() {
|
||||
current_source_sha="$(remote_source_sha)"
|
||||
if [ -z "$current_source_sha" ] || [ "$current_source_sha" = "$GITHUB_SHA" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if git -C "$GITHUB_WORKSPACE" merge-base --is-ancestor "$GITHUB_SHA" "$current_source_sha"; then
|
||||
echo "Skipping stale publish sync for $GITHUB_SHA; origin/main already mirrors $current_source_sha."
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
if git diff --quiet -- docs .openclaw-sync; then
|
||||
echo "No publish-repo changes."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git fetch origin main:refs/remotes/origin/main; then
|
||||
skip_stale_source
|
||||
fi
|
||||
|
||||
git config user.name "openclaw-docs-sync[bot]"
|
||||
git config user.email "openclaw-docs-sync[bot]@users.noreply.github.com"
|
||||
git add docs .openclaw-sync
|
||||
git commit -m "chore(sync): mirror docs from $GITHUB_REPOSITORY@$GITHUB_SHA"
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if git fetch origin main:refs/remotes/origin/main; then
|
||||
skip_stale_source
|
||||
if git rebase -X theirs origin/main && git push origin HEAD:main; then
|
||||
exit 0
|
||||
fi
|
||||
git fetch origin main
|
||||
git rebase origin/main
|
||||
if git push origin HEAD:main; then
|
||||
exit 0
|
||||
fi
|
||||
git rebase --abort >/dev/null 2>&1 || true
|
||||
echo "Publish sync attempt ${attempt} failed; retrying."
|
||||
echo "Push attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
done
|
||||
|
||||
|
||||
@@ -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,14 +28,10 @@ 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 \
|
||||
translate-pl-release \
|
||||
translate-th-release
|
||||
translate-pl-release
|
||||
do
|
||||
gh api repos/openclaw/docs/dispatches \
|
||||
--method POST \
|
||||
|
||||
39
.github/workflows/docs.yml
vendored
39
.github/workflows/docs.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "**/*.md"
|
||||
- "docs/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ format('{0}-{1}', github.workflow, github.ref) }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
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: Check docs
|
||||
run: pnpm check:docs
|
||||
59
.github/workflows/duplicate-after-merge.yml
vendored
59
.github/workflows/duplicate-after-merge.yml
vendored
@@ -1,59 +0,0 @@
|
||||
name: Duplicate PRs After Merge
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
landed_pr:
|
||||
description: "Merged PR number that supersedes the duplicates"
|
||||
required: true
|
||||
type: string
|
||||
duplicate_prs:
|
||||
description: "Comma or whitespace separated duplicate PR numbers to close"
|
||||
required: true
|
||||
type: string
|
||||
apply:
|
||||
description: "When true, label/comment/close; otherwise dry-run only"
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: duplicate-after-merge-${{ github.event.inputs.landed_pr }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
close-duplicates:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Close confirmed duplicates
|
||||
env:
|
||||
APPLY: ${{ inputs.apply }}
|
||||
DUPLICATE_PRS: ${{ inputs.duplicate_prs }}
|
||||
LANDED_PR: ${{ inputs.landed_pr }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
args=(
|
||||
--repo "$REPO"
|
||||
--landed-pr "$LANDED_PR"
|
||||
--duplicates "$DUPLICATE_PRS"
|
||||
)
|
||||
|
||||
if [[ "$APPLY" == "true" ]]; then
|
||||
args+=(--apply)
|
||||
fi
|
||||
|
||||
node scripts/close-duplicate-prs-after-merge.mjs "${args[@]}"
|
||||
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"
|
||||
473
.github/workflows/install-smoke.yml
vendored
473
.github/workflows/install-smoke.yml
vendored
@@ -1,105 +1,83 @@
|
||||
name: Install Smoke
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "17 3 * * *"
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review, converted_to_draft]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_bun_global_install_smoke:
|
||||
description: Run the Bun global install image-provider smoke
|
||||
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:
|
||||
description: Git ref to validate
|
||||
required: false
|
||||
type: string
|
||||
run_bun_global_install_smoke:
|
||||
description: Run the Bun global install image-provider smoke
|
||||
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 == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || format('{0}-{1}', github.workflow, github.ref) }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
jobs:
|
||||
preflight:
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
docs_only: ${{ steps.manifest.outputs.docs_only }}
|
||||
run_install_smoke: ${{ steps.manifest.outputs.run_install_smoke }}
|
||||
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 }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Ensure preflight base commit
|
||||
uses: ./.github/actions/ensure-base-commit
|
||||
with:
|
||||
base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }}
|
||||
fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }}
|
||||
|
||||
- name: Detect docs-only changes
|
||||
id: docs_scope
|
||||
uses: ./.github/actions/detect-docs-changes
|
||||
|
||||
- name: Detect changed smoke scope
|
||||
id: changed_scope
|
||||
if: steps.docs_scope.outputs.docs_only != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
BASE="${{ github.event.before }}"
|
||||
else
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
fi
|
||||
|
||||
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
|
||||
|
||||
- name: Build install-smoke CI manifest
|
||||
id: manifest
|
||||
env:
|
||||
OPENCLAW_CI_EVENT_NAME: ${{ github.event_name }}
|
||||
OPENCLAW_CI_WORKFLOW_BUN_GLOBAL_INSTALL_SMOKE: ${{ inputs.run_bun_global_install_smoke || 'false' }}
|
||||
OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }}
|
||||
OPENCLAW_CI_RUN_CHANGED_SMOKE: ${{ steps.changed_scope.outputs.run_changed_smoke || 'false' }}
|
||||
run: |
|
||||
event_name="${OPENCLAW_CI_EVENT_NAME:-}"
|
||||
workflow_bun_global_install_smoke="${OPENCLAW_CI_WORKFLOW_BUN_GLOBAL_INSTALL_SMOKE:-false}"
|
||||
docs_only=false
|
||||
run_fast_install_smoke=true
|
||||
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}"
|
||||
if [ "$event_name" = "schedule" ]; then
|
||||
run_bun_global_install_smoke=true
|
||||
elif [ "$event_name" = "workflow_dispatch" ] || [ "$event_name" = "workflow_call" ]; then
|
||||
if [ "$workflow_bun_global_install_smoke" = "true" ]; then
|
||||
run_bun_global_install_smoke=true
|
||||
fi
|
||||
docs_only="${OPENCLAW_CI_DOCS_ONLY:-false}"
|
||||
run_changed_smoke="${OPENCLAW_CI_RUN_CHANGED_SMOKE:-false}"
|
||||
run_install_smoke=false
|
||||
if [ "$docs_only" != "true" ] && [ "$run_changed_smoke" = "true" ]; then
|
||||
run_install_smoke=true
|
||||
fi
|
||||
{
|
||||
echo "docs_only=$docs_only"
|
||||
echo "run_install_smoke=$run_install_smoke"
|
||||
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"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
install-smoke-fast:
|
||||
install-smoke:
|
||||
needs: [preflight]
|
||||
if: needs.preflight.outputs.run_fast_install_smoke == 'true' && needs.preflight.outputs.run_full_install_smoke != 'true'
|
||||
if: needs.preflight.outputs.run_install_smoke == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
@@ -107,51 +85,32 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout CLI
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
|
||||
# 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
|
||||
|
||||
# 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.
|
||||
- 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
|
||||
tags: openclaw-dockerfile-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}`);
|
||||
}
|
||||
}
|
||||
"
|
||||
'
|
||||
|
||||
- name: Run agents delete shared workspace Docker CLI smoke
|
||||
env:
|
||||
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
|
||||
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'
|
||||
|
||||
- name: Run Docker gateway network e2e
|
||||
env:
|
||||
@@ -159,6 +118,22 @@ jobs:
|
||||
OPENCLAW_GATEWAY_NETWORK_E2E_SKIP_BUILD: "1"
|
||||
run: bash scripts/e2e/gateway-network-docker.sh
|
||||
|
||||
# This smoke validates that the build-arg path preinstalls the matrix
|
||||
# runtime deps declared by the plugin and that matrix discovery stays
|
||||
# healthy in the final runtime image.
|
||||
- name: Build extension Dockerfile smoke image
|
||||
uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: |
|
||||
OPENCLAW_DOCKER_APT_UPGRADE=0
|
||||
OPENCLAW_EXTENSIONS=matrix
|
||||
tags: openclaw-ext-smoke:local
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
|
||||
- name: Smoke test Dockerfile with matrix extension build arg
|
||||
run: |
|
||||
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc '
|
||||
@@ -204,233 +179,28 @@ jobs:
|
||||
"
|
||||
'
|
||||
|
||||
root_dockerfile_image:
|
||||
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"
|
||||
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: 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@722e97d12b1d06a961800dd6c05d79d951ad3c80 # 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:
|
||||
IMAGE_REF: ${{ needs.preflight.outputs.dockerfile_image }}
|
||||
run: |
|
||||
timeout 45m docker buildx build \
|
||||
--progress=plain \
|
||||
--push \
|
||||
--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 }}
|
||||
|
||||
- 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
|
||||
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: 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'
|
||||
|
||||
- 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_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_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 '
|
||||
which openclaw &&
|
||||
openclaw --version &&
|
||||
node -e "
|
||||
const Module = require(\"node:module\");
|
||||
const matrixPackage = require(\"/app/extensions/matrix/package.json\");
|
||||
const requireFromMatrix = Module.createRequire(\"/app/extensions/matrix/package.json\");
|
||||
const runtimeDeps = Object.keys(matrixPackage.dependencies ?? {});
|
||||
if (runtimeDeps.length === 0) {
|
||||
throw new Error(
|
||||
\"matrix package has no declared runtime dependencies; smoke cannot validate install mirroring\",
|
||||
);
|
||||
}
|
||||
for (const dep of runtimeDeps) {
|
||||
requireFromMatrix.resolve(dep);
|
||||
}
|
||||
const { spawnSync } = require(\"node:child_process\");
|
||||
const run = spawnSync(\"openclaw\", [\"plugins\", \"list\", \"--json\"], { encoding: \"utf8\" });
|
||||
if (run.status !== 0) {
|
||||
process.stderr.write(run.stderr || run.stdout || \"plugins list failed\\n\");
|
||||
process.exit(run.status ?? 1);
|
||||
}
|
||||
const parsed = JSON.parse(run.stdout);
|
||||
const matrix = (parsed.plugins || []).find((entry) => entry.id === \"matrix\");
|
||||
if (!matrix) {
|
||||
throw new Error(\"matrix plugin missing from bundled plugin list\");
|
||||
}
|
||||
const matrixDiag = (parsed.diagnostics || []).filter(
|
||||
(diag) =>
|
||||
typeof diag.source === \"string\" &&
|
||||
diag.source.includes(\"/extensions/matrix\") &&
|
||||
typeof diag.message === \"string\" &&
|
||||
diag.message.includes(\"extension entry escapes package directory\"),
|
||||
);
|
||||
if (matrixDiag.length > 0) {
|
||||
throw new Error(
|
||||
\"unexpected matrix diagnostics: \" +
|
||||
matrixDiag.map((diag) => diag.message).join(\"; \"),
|
||||
);
|
||||
}
|
||||
"
|
||||
'
|
||||
|
||||
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@722e97d12b1d06a961800dd6c05d79d951ad3c80 # 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
|
||||
if: github.event_name != 'pull_request'
|
||||
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
|
||||
- name: Setup Node environment for local pack smoke
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
@@ -443,67 +213,27 @@ jobs:
|
||||
OPENCLAW_NO_ONBOARD: "1"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_CLI: "1"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_IMAGE_BUILD: "1"
|
||||
OPENCLAW_INSTALL_NONROOT_SKIP_IMAGE_BUILD: "1"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT: "0"
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL: "1"
|
||||
OPENCLAW_INSTALL_NONROOT_SKIP_IMAGE_BUILD: ${{ github.event_name == 'pull_request' && '0' || '1' }}
|
||||
OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
|
||||
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_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'
|
||||
if: needs.preflight.outputs.run_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"
|
||||
steps:
|
||||
- name: Checkout CLI
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1
|
||||
|
||||
- name: Setup Node environment for package smoke
|
||||
uses: ./.github/actions/setup-node-env
|
||||
@@ -514,5 +244,4 @@ jobs:
|
||||
- name: Run fast bundled plugin Docker E2E
|
||||
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 120s pnpm test:docker:bundled-channel-deps:fast
|
||||
|
||||
27
.github/workflows/labeler.yml
vendored
27
.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;
|
||||
}
|
||||
@@ -296,25 +295,6 @@ jobs:
|
||||
.filter((name) => typeof name === "string"),
|
||||
);
|
||||
|
||||
if (pullRequest.user?.type === "Bot" || /\[bot\]$/i.test(authorLogin) || authorLogin.startsWith("app/")) {
|
||||
if (labelNames.has(activePrLimitLabel)) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pullRequest.number,
|
||||
name: activePrLimitLabel,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error?.status !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
core.info(`Skipping active PR limit for GitHub App author ${authorLogin}.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (labelNames.has(activePrLimitOverrideLabel)) {
|
||||
if (labelNames.has(activePrLimitLabel)) {
|
||||
try {
|
||||
@@ -394,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@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
- name: Build and push live media runner image
|
||||
uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # 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
|
||||
15
.github/workflows/macos-release.yml
vendored
15
.github/workflows/macos-release.yml
vendored
@@ -12,11 +12,6 @@ on:
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
public_release_branch:
|
||||
description: Public branch that contains the release tag commit, usually main or release/YYYY.M.D
|
||||
required: false
|
||||
default: main
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: macos-release-${{ inputs.tag }}
|
||||
@@ -71,17 +66,13 @@ jobs:
|
||||
- name: Validate release tag and package metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
PUBLIC_RELEASE_BRANCH: ${{ inputs.public_release_branch }}
|
||||
WORKFLOW_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${PUBLIC_RELEASE_BRANCH}" != "main" && ! "${PUBLIC_RELEASE_BRANCH}" =~ ^release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
|
||||
echo "public_release_branch must be main or release/YYYY.M.D, got ${PUBLIC_RELEASE_BRANCH}." >&2
|
||||
exit 1
|
||||
fi
|
||||
RELEASE_SHA=$(git rev-parse HEAD)
|
||||
RELEASE_MAIN_REF="refs/remotes/origin/${PUBLIC_RELEASE_BRANCH}"
|
||||
RELEASE_MAIN_REF="refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
export RELEASE_SHA RELEASE_TAG RELEASE_MAIN_REF
|
||||
git fetch --no-tags origin "+refs/heads/${PUBLIC_RELEASE_BRANCH}:refs/remotes/origin/${PUBLIC_RELEASE_BRANCH}"
|
||||
git fetch --no-tags origin "+refs/heads/${WORKFLOW_REF_NAME}:refs/remotes/origin/${WORKFLOW_REF_NAME}"
|
||||
pnpm release:openclaw:npm:check
|
||||
|
||||
- name: Summarize next step
|
||||
|
||||
@@ -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}`);
|
||||
230
.github/workflows/npm-telegram-beta-e2e.yml
vendored
230
.github/workflows/npm-telegram-beta-e2e.yml
vendored
@@ -1,230 +0,0 @@
|
||||
name: NPM Telegram Beta E2E
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package_spec:
|
||||
description: Published OpenClaw package spec to test when no artifact is supplied
|
||||
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
|
||||
default: mock-openai
|
||||
type: choice
|
||||
options:
|
||||
- mock-openai
|
||||
- live-frontier
|
||||
scenario:
|
||||
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
|
||||
|
||||
concurrency:
|
||||
group: npm-telegram-beta-e2e-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.33.0"
|
||||
|
||||
jobs:
|
||||
run_package_telegram_e2e:
|
||||
name: Run package Telegram E2E
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
environment: qa-live-shared
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: "false"
|
||||
DOCKER_BUILD_RECORD_UPLOAD: "false"
|
||||
steps:
|
||||
- name: Checkout dispatch ref
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.harness_ref || github.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Set up Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@722e97d12b1d06a961800dd6c05d79d951ad3c80 # v1
|
||||
with:
|
||||
max-cache-size-mb: 800000
|
||||
|
||||
- name: Build Docker E2E image
|
||||
uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2
|
||||
with:
|
||||
context: .
|
||||
file: ./scripts/e2e/Dockerfile
|
||||
target: build
|
||||
platforms: linux/amd64
|
||||
tags: openclaw-docker-e2e:local
|
||||
load: true
|
||||
push: false
|
||||
provenance: false
|
||||
|
||||
- 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 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 }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
shell: bash
|
||||
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
|
||||
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"
|
||||
if [[ -z "${!key:-}" ]]; then
|
||||
echo "Missing required ${key}." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_var OPENCLAW_QA_CONVEX_SITE_URL
|
||||
require_var OPENCLAW_QA_CONVEX_SECRET_CI
|
||||
if [[ "${PROVIDER_MODE}" == "live-frontier" ]]; then
|
||||
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
|
||||
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_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
|
||||
|
||||
output_dir=".artifacts/qa-e2e/npm-telegram-beta-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
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
|
||||
|
||||
pnpm test:docker:npm-telegram-live
|
||||
|
||||
- name: Upload npm Telegram E2E artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: npm-telegram-beta-e2e-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: .artifacts/qa-e2e/
|
||||
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
|
||||
@@ -541,35 +432,24 @@ jobs:
|
||||
OPENCLAW_DISCORD_SMOKE_CHANNEL_ID: ${{ secrets.OPENCLAW_DISCORD_SMOKE_CHANNEL_ID }}
|
||||
OPENCLAW_RELEASE_CHECK_OS: ${{ matrix.os_id }}
|
||||
OPENCLAW_RELEASE_CHECK_RUNNER: ${{ matrix.runner }}
|
||||
CANDIDATE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}
|
||||
CANDIDATE_VERSION: ${{ needs.prepare.outputs.candidate_version }}
|
||||
SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }}
|
||||
BASELINE_SPEC: ${{ needs.prepare.outputs.baseline_spec }}
|
||||
PREVIOUS_VERSION: ${{ inputs.previous_version }}
|
||||
BASELINE_TGZ: ${{ runner.temp }}/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}
|
||||
PROVIDER: ${{ inputs.provider }}
|
||||
MODE: ${{ matrix.lane }}
|
||||
SUITE: ${{ matrix.suite }}
|
||||
REF: ${{ inputs.ref }}
|
||||
OUTPUT_DIR: ${{ runner.temp }}/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}
|
||||
run: |
|
||||
DISCORD_ARGS=()
|
||||
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 \
|
||||
--candidate-tgz "${CANDIDATE_TGZ}" \
|
||||
--candidate-version "${CANDIDATE_VERSION}" \
|
||||
--source-sha "${SOURCE_SHA}" \
|
||||
--baseline-spec "${BASELINE_SPEC}" \
|
||||
--previous-version "${PREVIOUS_VERSION}" \
|
||||
--baseline-tgz "${BASELINE_TGZ}" \
|
||||
--provider "${PROVIDER}" \
|
||||
--mode "${MODE}" \
|
||||
--suite "${SUITE}" \
|
||||
--ref "${REF}" \
|
||||
pnpm dlx "tsx@${TSX_VERSION}" workflow/scripts/openclaw-cross-os-release-checks.ts \
|
||||
--candidate-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/candidate/${{ needs.prepare.outputs.candidate_file_name }}" \
|
||||
--candidate-version "${{ needs.prepare.outputs.candidate_version }}" \
|
||||
--source-sha "${{ needs.prepare.outputs.source_sha }}" \
|
||||
--baseline-spec "${{ needs.prepare.outputs.baseline_spec }}" \
|
||||
--previous-version "${{ inputs.previous_version }}" \
|
||||
--baseline-tgz "$RUNNER_TEMP/openclaw-cross-os-release-checks/baseline/${{ needs.prepare.outputs.baseline_file_name }}" \
|
||||
--provider "${{ inputs.provider }}" \
|
||||
--mode "${{ matrix.lane }}" \
|
||||
--suite "${{ matrix.suite }}" \
|
||||
--ref "${{ inputs.ref }}" \
|
||||
"${DISCORD_ARGS[@]}" \
|
||||
--output-dir "${OUTPUT_DIR}"
|
||||
--output-dir "$RUNNER_TEMP/openclaw-cross-os-release-checks/${{ matrix.artifact_name }}-${{ matrix.suite }}"
|
||||
|
||||
- name: Summarize release checks
|
||||
if: always()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
4
.github/workflows/openclaw-npm-release.yml
vendored
4
.github/workflows/openclaw-npm-release.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
# so this public workflow can stay focused on OIDC publish only.
|
||||
preflight_openclaw_npm:
|
||||
if: ${{ inputs.preflight_only }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
@@ -252,7 +252,7 @@ jobs:
|
||||
|
||||
validate_publish_request:
|
||||
if: ${{ !inputs.preflight_only }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
|
||||
705
.github/workflows/openclaw-release-checks.yml
vendored
705
.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,25 @@ 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' }}
|
||||
|
||||
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 +58,80 @@ 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."
|
||||
echo "- This run will execute cross-OS release validation plus 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 }}
|
||||
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 +140,18 @@ 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 +160,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 }}
|
||||
@@ -488,360 +196,3 @@ jobs:
|
||||
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 }}
|
||||
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 }})
|
||||
needs: [resolve_target]
|
||||
if: contains(fromJSON('["all","qa","qa-parity"]'), needs.resolve_target.outputs.rerun_group)
|
||||
runs-on: blacksmith-8vcpu-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"
|
||||
OPENAI_API_KEY: ""
|
||||
ANTHROPIC_API_KEY: ""
|
||||
OPENCLAW_LIVE_OPENAI_KEY: ""
|
||||
OPENCLAW_LIVE_ANTHROPIC_KEY: ""
|
||||
OPENCLAW_LIVE_GEMINI_KEY: ""
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ""
|
||||
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: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run parity lane
|
||||
env:
|
||||
QA_PARITY_LANE: ${{ matrix.lane }}
|
||||
QA_PARITY_OUTPUT_DIR: ${{ matrix.output_dir }}
|
||||
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}"
|
||||
|
||||
- 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: Generate parity report
|
||||
run: |
|
||||
pnpm openclaw qa parity-report \
|
||||
--repo-root . \
|
||||
--candidate-summary .artifacts/qa-e2e/gpt54/qa-suite-summary.json \
|
||||
--baseline-summary .artifacts/qa-e2e/opus46/qa-suite-summary.json \
|
||||
--candidate-label "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--baseline-label anthropic/claude-opus-4-6 \
|
||||
--output-dir .artifacts/qa-e2e/parity
|
||||
|
||||
- name: Upload parity artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-qa-parity-${{ needs.resolve_target.outputs.revision }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
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
|
||||
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 }}
|
||||
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 }}
|
||||
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:
|
||||
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
|
||||
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
require_var() {
|
||||
local key="$1"
|
||||
if [[ -z "${!key:-}" ]]; then
|
||||
echo "Missing required ${key}." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_var OPENCLAW_QA_CONVEX_SITE_URL
|
||||
require_var OPENCLAW_QA_CONVEX_SECRET_CI
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Telegram live lane
|
||||
id: run_lane
|
||||
shell: bash
|
||||
env:
|
||||
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"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
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
|
||||
|
||||
- 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/
|
||||
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"
|
||||
|
||||
@@ -7,7 +7,6 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
@@ -21,13 +20,12 @@ jobs:
|
||||
live_and_openwebui_checks:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
include_repo_e2e: true
|
||||
include_release_path_suites: true
|
||||
include_release_path_suites: false
|
||||
include_openwebui: true
|
||||
include_live_suites: true
|
||||
secrets:
|
||||
@@ -38,7 +36,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 }}
|
||||
@@ -75,4 +72,3 @@ jobs:
|
||||
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 }}
|
||||
|
||||
70
.github/workflows/opengrep-precise-full.yml
vendored
70
.github/workflows/opengrep-precise-full.yml
vendored
@@ -1,70 +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
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
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@v6
|
||||
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@v4
|
||||
# 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
|
||||
100
.github/workflows/opengrep-precise.yml
vendored
100
.github/workflows/opengrep-precise.yml
vendored
@@ -1,100 +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:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- ".github/actions/ensure-base-commit/**"
|
||||
- ".github/workflows/opengrep-precise.yml"
|
||||
- ".github/workflows/opengrep-precise-full.yml"
|
||||
- ".semgrepignore"
|
||||
- "apps/**"
|
||||
- "extensions/**"
|
||||
- "packages/**"
|
||||
- "scripts/**"
|
||||
- "security/opengrep/**"
|
||||
- "src/**"
|
||||
|
||||
concurrency:
|
||||
group: opengrep-pr-diff-${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
scan:
|
||||
name: Scan changed paths (precise)
|
||||
if: ${{ !github.event.pull_request.draft }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
fetch-tags: false
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Ensure PR base commit
|
||||
uses: ./.github/actions/ensure-base-commit
|
||||
with:
|
||||
base-sha: ${{ github.event.pull_request.base.sha }}
|
||||
fetch-ref: ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
- 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@v4
|
||||
# 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 upgrade-survivor published-upgrade-survivor bundled-channel-deps-compat plugins-offline plugin-update"
|
||||
;;
|
||||
product)
|
||||
docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor 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"
|
||||
14
.github/workflows/parity-gate.yml
vendored
14
.github/workflows/parity-gate.yml
vendored
@@ -13,7 +13,6 @@ on:
|
||||
- "src/gateway/**"
|
||||
- "src/media/**"
|
||||
- ".github/workflows/parity-gate.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -24,7 +23,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
parity-gate:
|
||||
name: Run the OpenAI / Opus 4.6 parity gate against the qa-lab mock
|
||||
name: Run the GPT-5.4 / Opus 4.6 parity gate against the qa-lab mock
|
||||
if: ${{ github.event.pull_request.draft != true }}
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
@@ -42,7 +41,6 @@ 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_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
|
||||
OPENAI_API_KEY: ""
|
||||
ANTHROPIC_API_KEY: ""
|
||||
@@ -57,11 +55,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
|
||||
@@ -78,13 +74,13 @@ jobs:
|
||||
# The approval-turn sentinel still runs inside the full parity pack below.
|
||||
# Keep the exact mock read-plan contract in deterministic unit tests instead
|
||||
# of paying for a separate full-runtime preflight that has been flaky in CI.
|
||||
- name: Run OpenAI candidate lane
|
||||
- name: Run GPT-5.4 lane
|
||||
run: |
|
||||
pnpm openclaw qa suite \
|
||||
--provider-mode mock-openai \
|
||||
--parity-pack agentic \
|
||||
--concurrency "${QA_PARITY_CONCURRENCY}" \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--model openai/gpt-5.4 \
|
||||
--alt-model openai/gpt-5.4-alt \
|
||||
--output-dir .artifacts/qa-e2e/gpt54
|
||||
|
||||
@@ -104,7 +100,7 @@ jobs:
|
||||
--repo-root . \
|
||||
--candidate-summary .artifacts/qa-e2e/gpt54/qa-suite-summary.json \
|
||||
--baseline-summary .artifacts/qa-e2e/opus46/qa-suite-summary.json \
|
||||
--candidate-label "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--candidate-label openai/gpt-5.4 \
|
||||
--baseline-label anthropic/claude-opus-4-6 \
|
||||
--output-dir .artifacts/qa-e2e/parity
|
||||
|
||||
|
||||
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"
|
||||
556
.github/workflows/qa-live-transports-convex.yml
vendored
556
.github/workflows/qa-live-transports-convex.yml
vendored
@@ -1,556 +0,0 @@
|
||||
name: QA-Lab - All Lanes
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "41 4 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Ref, tag, or SHA to run
|
||||
required: true
|
||||
default: main
|
||||
type: string
|
||||
scenario:
|
||||
description: Optional comma-separated Telegram scenario ids
|
||||
required: false
|
||||
type: string
|
||||
discord_scenario:
|
||||
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
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: qa-lab-all-lanes-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
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_BUILD_PRIVATE_QA: "1"
|
||||
OPENCLAW_ENABLE_PRIVATE_QA_CLI: "1"
|
||||
|
||||
jobs:
|
||||
authorize_actor:
|
||||
name: Authorize workflow actor
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Require maintainer-level repository access
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
if (context.eventName === "schedule") {
|
||||
core.info("Scheduled default-branch QA run; actor permission check is only required for manual dispatch.");
|
||||
return;
|
||||
}
|
||||
const allowed = new Set(["admin", "maintain", "write"]);
|
||||
const { owner, repo } = context.repo;
|
||||
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner,
|
||||
repo,
|
||||
username: context.actor,
|
||||
});
|
||||
const permission = data.permission;
|
||||
core.info(`Actor ${context.actor} permission: ${permission}`);
|
||||
if (!allowed.has(permission)) {
|
||||
core.setFailed(
|
||||
`Workflow requires write/maintain/admin access. Actor "${context.actor}" has "${permission}".`,
|
||||
);
|
||||
}
|
||||
|
||||
validate_selected_ref:
|
||||
name: Validate selected ref
|
||||
needs: authorize_actor
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
outputs:
|
||||
selected_revision: ${{ steps.validate.outputs.selected_revision }}
|
||||
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
|
||||
|
||||
- name: Validate selected ref
|
||||
id: validate
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
INPUT_REF: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
selected_revision="$(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
|
||||
trusted_reason="main-ancestor"
|
||||
elif git tag --points-at "$selected_revision" | 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
|
||||
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'
|
||||
)"
|
||||
if [[ "$pr_head_count" != "0" ]]; then
|
||||
trusted_reason="open-pr-head"
|
||||
fi
|
||||
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 "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 "trusted_reason=$trusted_reason" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "Validated ref: \`${INPUT_REF}\`"
|
||||
echo "Resolved SHA: \`$selected_revision\`"
|
||||
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
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
QA_PARITY_CONCURRENCY: "1"
|
||||
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
|
||||
OPENAI_API_KEY: ""
|
||||
ANTHROPIC_API_KEY: ""
|
||||
OPENCLAW_LIVE_OPENAI_KEY: ""
|
||||
OPENCLAW_LIVE_ANTHROPIC_KEY: ""
|
||||
OPENCLAW_LIVE_GEMINI_KEY: ""
|
||||
OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ""
|
||||
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: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run OpenAI candidate lane
|
||||
run: |
|
||||
pnpm openclaw qa suite \
|
||||
--provider-mode mock-openai \
|
||||
--parity-pack agentic \
|
||||
--concurrency "${QA_PARITY_CONCURRENCY}" \
|
||||
--model "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--alt-model openai/gpt-5.4-alt \
|
||||
--output-dir .artifacts/qa-e2e/gpt54
|
||||
|
||||
- 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: |
|
||||
pnpm openclaw qa parity-report \
|
||||
--repo-root . \
|
||||
--candidate-summary .artifacts/qa-e2e/gpt54/qa-suite-summary.json \
|
||||
--baseline-summary .artifacts/qa-e2e/opus46/qa-suite-summary.json \
|
||||
--candidate-label "${OPENCLAW_CI_OPENAI_MODEL}" \
|
||||
--baseline-label anthropic/claude-opus-4-6 \
|
||||
--output-dir .artifacts/qa-e2e/parity
|
||||
|
||||
- name: Upload parity artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qa-parity-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: .artifacts/qa-e2e/
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
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
|
||||
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 }}
|
||||
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
|
||||
id: run_lane
|
||||
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=(
|
||||
--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()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qa-live-matrix-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
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
|
||||
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 }}
|
||||
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
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
require_var() {
|
||||
local key="$1"
|
||||
if [[ -z "${!key:-}" ]]; then
|
||||
echo "Missing required ${key}." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_var OPENAI_API_KEY
|
||||
require_var OPENCLAW_QA_CONVEX_SITE_URL
|
||||
require_var OPENCLAW_QA_CONVEX_SECRET_CI
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Telegram live lane
|
||||
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"
|
||||
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
|
||||
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
output_dir=".artifacts/qa-e2e/telegram-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
scenario_args=()
|
||||
|
||||
if [[ -n "${INPUT_SCENARIO// }" ]]; then
|
||||
IFS=',' read -r -a raw_scenarios <<<"${INPUT_SCENARIO}"
|
||||
for raw in "${raw_scenarios[@]}"; do
|
||||
scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
||||
if [[ -n "${scenario}" ]]; then
|
||||
scenario_args+=(--scenario "${scenario}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
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 \
|
||||
"${scenario_args[@]}"
|
||||
|
||||
- name: Upload Telegram QA artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qa-live-telegram-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
run_live_discord:
|
||||
name: Run Discord live QA lane with Convex leases
|
||||
needs: [authorize_actor, validate_selected_ref]
|
||||
runs-on: blacksmith-8vcpu-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 }}
|
||||
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
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
require_var() {
|
||||
local key="$1"
|
||||
if [[ -z "${!key:-}" ]]; then
|
||||
echo "Missing required ${key}." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_var OPENAI_API_KEY
|
||||
require_var OPENCLAW_QA_CONVEX_SITE_URL
|
||||
require_var OPENCLAW_QA_CONVEX_SECRET_CI
|
||||
|
||||
- name: Build private QA runtime
|
||||
run: pnpm build
|
||||
|
||||
- name: Run Discord live lane
|
||||
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"
|
||||
OPENCLAW_QA_DISCORD_CAPTURE_CONTENT: "1"
|
||||
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.discord_scenario || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
output_dir=".artifacts/qa-e2e/discord-live-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
scenario_args=()
|
||||
|
||||
if [[ -n "${INPUT_SCENARIO// }" ]]; then
|
||||
IFS=',' read -r -a raw_scenarios <<<"${INPUT_SCENARIO}"
|
||||
for raw in "${raw_scenarios[@]}"; do
|
||||
scenario="$(printf '%s' "${raw}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
||||
if [[ -n "${scenario}" ]]; then
|
||||
scenario_args+=(--scenario "${scenario}")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
pnpm openclaw qa discord \
|
||||
--repo-root . \
|
||||
--output-dir "${output_dir}" \
|
||||
--provider-mode live-frontier \
|
||||
--model openai/gpt-5.4 \
|
||||
--alt-model openai/gpt-5.4 \
|
||||
--fast \
|
||||
--credential-source convex \
|
||||
--credential-role ci \
|
||||
"${scenario_args[@]}"
|
||||
|
||||
- name: Upload Discord QA artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: qa-live-discord-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: ${{ steps.run_lane.outputs.output_dir }}
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
399
.github/workflows/stale.yml
vendored
399
.github/workflows/stale.yml
vendored
@@ -4,32 +4,6 @@ on:
|
||||
schedule:
|
||||
- cron: "17 3 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
backfill_stale_closures:
|
||||
description: "Close currently stale-eligible issues and PRs with the Barnacle app"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
dry_run:
|
||||
description: "List matching stale-eligible items without closing them"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
include_issues:
|
||||
description: "Include stale-eligible issues in the backfill"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
include_prs:
|
||||
description: "Include stale-eligible pull requests in the backfill"
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
max_closures:
|
||||
description: "Maximum items to close when dry_run is false"
|
||||
required: false
|
||||
type: number
|
||||
default: 50
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -38,7 +12,6 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.backfill_stale_closures != true }}
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
@@ -56,19 +29,19 @@ 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
|
||||
with:
|
||||
repo-token: ${{ steps.app-token.outputs.token || steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 14
|
||||
days-before-issue-close: 7
|
||||
days-before-pr-stale: 14
|
||||
days-before-pr-close: 7
|
||||
days-before-issue-stale: 7
|
||||
days-before-issue-close: 5
|
||||
days-before-pr-stale: 5
|
||||
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
|
||||
@@ -83,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: 7
|
||||
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
|
||||
@@ -161,18 +86,18 @@ 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:
|
||||
repo-token: ${{ steps.app-token-fallback.outputs.token }}
|
||||
days-before-issue-stale: 14
|
||||
days-before-issue-close: 7
|
||||
days-before-pr-stale: 14
|
||||
days-before-pr-close: 7
|
||||
days-before-issue-stale: 7
|
||||
days-before-issue-close: 5
|
||||
days-before-pr-stale: 5
|
||||
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
|
||||
@@ -187,306 +112,14 @@ 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.
|
||||
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: 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: 7
|
||||
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.
|
||||
That channel is the escape hatch for high-quality PRs that get auto-closed.
|
||||
|
||||
backfill-stale-closures:
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.backfill_stale_closures == true }}
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v3
|
||||
id: app-token
|
||||
with:
|
||||
app-id: "2971289"
|
||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY_FALLBACK }}
|
||||
- name: Backfill stale closures
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
INCLUDE_ISSUES: ${{ inputs.include_issues }}
|
||||
INCLUDE_PRS: ${{ inputs.include_prs }}
|
||||
MAX_CLOSURES: ${{ inputs.max_closures }}
|
||||
with:
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const dayMs = 24 * 60 * 60 * 1000;
|
||||
const dryRun = process.env.DRY_RUN !== "false";
|
||||
const includeIssues = process.env.INCLUDE_ISSUES !== "false";
|
||||
const includePrs = process.env.INCLUDE_PRS !== "false";
|
||||
const maxClosures = Math.max(0, Number(process.env.MAX_CLOSURES || "50"));
|
||||
const nowMs = Date.now();
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
const issueExemptLabels = new Set([
|
||||
"enhancement",
|
||||
"maintainer",
|
||||
"pinned",
|
||||
"security",
|
||||
"no-stale",
|
||||
"bad-barnacle",
|
||||
]);
|
||||
const prExemptLabels = new Set(["maintainer", "no-stale", "bad-barnacle"]);
|
||||
const maintainerAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
|
||||
const maintainerLogins = new Set([
|
||||
"altaywtf",
|
||||
"BunsDev",
|
||||
"cpojer",
|
||||
"gumadeiras",
|
||||
"hydro13",
|
||||
"hxy91819",
|
||||
"jalehman",
|
||||
"joshavant",
|
||||
"joshp123",
|
||||
"mbelinky",
|
||||
"mukhtharcm",
|
||||
"ngutman",
|
||||
"obviyus",
|
||||
"odysseus0",
|
||||
"onutc",
|
||||
"osolmaz",
|
||||
"sebslight",
|
||||
"sliverp",
|
||||
"steipete",
|
||||
"thewilloftheshadow",
|
||||
"tyler6204",
|
||||
"velvet-shark",
|
||||
"vignesh07",
|
||||
"vincentkoc",
|
||||
"visionik",
|
||||
].map(login => login.toLowerCase()));
|
||||
|
||||
const issueCloseMessage = [
|
||||
"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.",
|
||||
].join("\n");
|
||||
const prCloseMessage = [
|
||||
"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.",
|
||||
].join("\n");
|
||||
|
||||
const hasAny = (labels, exemptLabels) => {
|
||||
for (const label of labels) {
|
||||
if (exemptLabels.has(label)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const isOlderThan = (dateString, days) => {
|
||||
const timestamp = Date.parse(dateString);
|
||||
return Number.isFinite(timestamp) && timestamp < nowMs - days * dayMs;
|
||||
};
|
||||
|
||||
const candidates = [];
|
||||
const skipped = {
|
||||
missingStale: 0,
|
||||
exemptLabel: 0,
|
||||
maintainerAuthor: 0,
|
||||
maintainerAssignee: 0,
|
||||
notOldEnough: 0,
|
||||
disabledType: 0,
|
||||
};
|
||||
|
||||
for await (const response of github.paginate.iterator(github.rest.issues.listForRepo, {
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
sort: "updated",
|
||||
direction: "asc",
|
||||
per_page: 100,
|
||||
})) {
|
||||
for (const item of response.data) {
|
||||
const isPr = Boolean(item.pull_request);
|
||||
if ((isPr && !includePrs) || (!isPr && !includeIssues)) {
|
||||
skipped.disabledType += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const labels = new Set((item.labels || []).map(label => label.name));
|
||||
if (!labels.has("stale")) {
|
||||
skipped.missingStale += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const exemptLabels = isPr ? prExemptLabels : issueExemptLabels;
|
||||
if (hasAny(labels, exemptLabels)) {
|
||||
skipped.exemptLabel += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (maintainerAssociations.has(item.author_association)) {
|
||||
skipped.maintainerAuthor += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const assigned = (item.assignees || []).length > 0;
|
||||
const assignedToMaintainer = (item.assignees || []).some(assignee =>
|
||||
maintainerLogins.has(assignee.login.toLowerCase()),
|
||||
);
|
||||
if (assignedToMaintainer) {
|
||||
skipped.maintainerAssignee += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let eligible = false;
|
||||
let lane = "";
|
||||
if (isPr && assigned) {
|
||||
lane = "assigned-pr";
|
||||
eligible = isOlderThan(item.created_at, 34) && isOlderThan(item.updated_at, 7);
|
||||
} else if (isPr) {
|
||||
lane = "unassigned-pr";
|
||||
eligible = isOlderThan(item.updated_at, 7);
|
||||
} else if (assigned) {
|
||||
lane = "assigned-issue";
|
||||
eligible = isOlderThan(item.updated_at, 10);
|
||||
} else {
|
||||
lane = "unassigned-issue";
|
||||
eligible = isOlderThan(item.updated_at, 7);
|
||||
}
|
||||
|
||||
if (!eligible) {
|
||||
skipped.notOldEnough += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.push({
|
||||
number: item.number,
|
||||
title: item.title,
|
||||
lane,
|
||||
isPr,
|
||||
assigned,
|
||||
createdAt: item.created_at,
|
||||
updatedAt: item.updated_at,
|
||||
authorAssociation: item.author_association,
|
||||
url: item.html_url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const countsByLane = candidates.reduce((counts, candidate) => {
|
||||
counts[candidate.lane] = (counts[candidate.lane] || 0) + 1;
|
||||
return counts;
|
||||
}, {});
|
||||
const selected = candidates.slice(0, maxClosures);
|
||||
|
||||
core.info(`Dry run: ${dryRun}`);
|
||||
core.info(`Candidates: ${candidates.length}`);
|
||||
core.info(`Selected: ${selected.length}`);
|
||||
core.info(`Counts by lane: ${JSON.stringify(countsByLane)}`);
|
||||
core.info(`Skipped: ${JSON.stringify(skipped)}`);
|
||||
for (const candidate of selected) {
|
||||
core.info(`${dryRun ? "Would close" : "Closing"} ${candidate.lane} #${candidate.number}: ${candidate.title} (${candidate.url})`);
|
||||
}
|
||||
|
||||
await core.summary
|
||||
.addHeading("Stale Closure Backfill")
|
||||
.addRaw(`Dry run: ${dryRun}\n\n`)
|
||||
.addRaw(`Candidates: ${candidates.length}\n\n`)
|
||||
.addRaw(`Selected: ${selected.length}\n\n`)
|
||||
.addCodeBlock(JSON.stringify({ countsByLane, skipped }, null, 2), "json")
|
||||
.addTable([
|
||||
[
|
||||
{ data: "Lane", header: true },
|
||||
{ data: "Number", header: true },
|
||||
{ data: "Title", header: true },
|
||||
{ data: "URL", header: true },
|
||||
],
|
||||
...selected.map(candidate => [
|
||||
candidate.lane,
|
||||
String(candidate.number),
|
||||
candidate.title,
|
||||
candidate.url,
|
||||
]),
|
||||
])
|
||||
.write();
|
||||
|
||||
if (dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const candidate of selected) {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: candidate.number,
|
||||
body: candidate.isPr ? prCloseMessage : issueCloseMessage,
|
||||
});
|
||||
|
||||
if (candidate.isPr) {
|
||||
await github.rest.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: candidate.number,
|
||||
state: "closed",
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.update({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: candidate.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lock-closed-issues:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.backfill_stale_closures != true }}
|
||||
permissions:
|
||||
issues: write
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
|
||||
279
.github/workflows/test-performance-agent.yml
vendored
279
.github/workflows/test-performance-agent.yml
vendored
@@ -1,279 +0,0 @@
|
||||
name: Test Performance Agent
|
||||
|
||||
on:
|
||||
workflow_run: # zizmor: ignore[dangerous-triggers] main-only test optimization after trusted CI; job gates repository, event, branch, actor, conclusion, current main SHA, and daily cadence before using write token
|
||||
workflows:
|
||||
- CI
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: test-performance-agent-main
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
TEST_PERF_BEFORE: .artifacts/test-perf/baseline-before.json
|
||||
TEST_PERF_AFTER: .artifacts/test-perf/after-agent.json
|
||||
TEST_PERF_COMPARE: .artifacts/test-perf/agent-compare.json
|
||||
|
||||
jobs:
|
||||
optimize-tests:
|
||||
if: >
|
||||
github.repository == 'openclaw/openclaw' &&
|
||||
(github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_branch == 'main' &&
|
||||
!endsWith(github.event.workflow_run.actor.login, '[bot]')))
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 240
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Gate trusted main activity and daily cadence
|
||||
id: gate
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$EVENT_NAME" != "workflow_run" ]; then
|
||||
echo "run_agent=true" >> "$GITHUB_OUTPUT"
|
||||
echo "base_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if git fetch --no-tags origin main; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" = "5" ]; then
|
||||
echo "Failed to fetch main after retries." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Fetch attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
done
|
||||
|
||||
remote_main="$(git rev-parse origin/main)"
|
||||
if [ "$remote_main" != "$WORKFLOW_HEAD_SHA" ]; then
|
||||
echo "CI run is superseded by ${remote_main}; skipping test performance agent for ${WORKFLOW_HEAD_SHA}."
|
||||
echo "run_agent=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
day_start="$(date -u +%Y-%m-%dT00:00:00Z)"
|
||||
runs_json="$RUNNER_TEMP/test-performance-agent-runs.json"
|
||||
gh api --method GET "repos/${GITHUB_REPOSITORY}/actions/workflows/test-performance-agent.yml/runs" \
|
||||
-f branch=main \
|
||||
-f event=workflow_run \
|
||||
-f per_page=50 > "$runs_json"
|
||||
|
||||
prior_runs="$(
|
||||
jq -r \
|
||||
--argjson current_run_id "$GITHUB_RUN_ID" \
|
||||
--arg day_start "$day_start" \
|
||||
'.workflow_runs[]
|
||||
| select(.database_id != $current_run_id)
|
||||
| select(.created_at >= $day_start)
|
||||
| select(.status != "cancelled")
|
||||
| select((.conclusion // "") != "skipped")
|
||||
| [.database_id, .status, (.conclusion // ""), .created_at, .head_sha]
|
||||
| @tsv' "$runs_json"
|
||||
)"
|
||||
|
||||
if [ -n "$prior_runs" ]; then
|
||||
echo "Test performance agent already ran or is running today; skipping."
|
||||
printf '%s\n' "$prior_runs"
|
||||
echo "run_agent=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "run_agent=true" >> "$GITHUB_OUTPUT"
|
||||
echo "base_sha=${remote_main}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Node environment
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Ensure test performance agent key exists
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENCLAW_TEST_PERF_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${OPENAI_API_KEY:-}" ]; then
|
||||
echo "Missing OPENCLAW_TEST_PERF_AGENT_OPENAI_API_KEY or OPENAI_API_KEY secret." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build baseline full-suite performance report
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
run: pnpm test:perf:groups --full-suite --allow-failures --output "$TEST_PERF_BEFORE" --limit 20 --top-files 40
|
||||
|
||||
- name: Run Codex test performance agent
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
|
||||
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
|
||||
model: ${{ vars.OPENCLAW_CI_OPENAI_MODEL_BARE }}
|
||||
effort: high
|
||||
sandbox: workspace-write
|
||||
safety-strategy: drop-sudo
|
||||
codex-args: '["--full-auto"]'
|
||||
|
||||
- name: Enforce focused test performance patch
|
||||
if: steps.gate.outputs.run_agent == 'true'
|
||||
id: patch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
untracked="$(git ls-files --others --exclude-standard)"
|
||||
if [ -n "$untracked" ]; then
|
||||
echo "Test performance agent created untracked files; forbidden:"
|
||||
printf '%s\n' "$untracked"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
added_deleted_or_renamed="$(git diff --name-status --diff-filter=ADR)"
|
||||
if [ -n "$added_deleted_or_renamed" ]; then
|
||||
echo "Test performance agent added, deleted, or renamed tracked files; forbidden:"
|
||||
printf '%s\n' "$added_deleted_or_renamed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bad_paths="$(
|
||||
git diff --name-only | while IFS= read -r path; do
|
||||
case "$path" in
|
||||
apps/*|extensions/*|packages/*|scripts/*|src/*|Swabble/*|test/*|ui/*) ;;
|
||||
*) printf '%s\n' "$path" ;;
|
||||
esac
|
||||
done
|
||||
)"
|
||||
if [ -n "$bad_paths" ]; then
|
||||
echo "Test performance agent touched forbidden paths:"
|
||||
printf '%s\n' "$bad_paths"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- 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
|
||||
set -euo pipefail
|
||||
export PATH="${NODE_BIN}:${PATH}"
|
||||
echo "${NODE_BIN}" >> "$GITHUB_PATH"
|
||||
node -v
|
||||
corepack enable
|
||||
pnpm -v
|
||||
|
||||
- name: Run full-suite performance report after agent changes
|
||||
if: steps.gate.outputs.run_agent == 'true' && steps.patch.outputs.has_changes == 'true'
|
||||
run: pnpm test:perf:groups --full-suite --output "$TEST_PERF_AFTER" --limit 20 --top-files 40
|
||||
|
||||
- name: Compare test performance reports
|
||||
if: steps.gate.outputs.run_agent == 'true' && steps.patch.outputs.has_changes == 'true'
|
||||
run: pnpm test:perf:groups:compare "$TEST_PERF_BEFORE" "$TEST_PERF_AFTER" --output "$TEST_PERF_COMPARE" --limit 20 --top-files 40
|
||||
|
||||
- name: Enforce coverage-preserving test count
|
||||
if: steps.gate.outputs.run_agent == 'true' && steps.patch.outputs.has_changes == 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs");
|
||||
const before = JSON.parse(fs.readFileSync(process.env.TEST_PERF_BEFORE, "utf8"));
|
||||
const after = JSON.parse(fs.readFileSync(process.env.TEST_PERF_AFTER, "utf8"));
|
||||
|
||||
if (before.failed) {
|
||||
console.log("Baseline had failing configs; skipping total test-count comparison against partial report.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const beforeTests = before.totals?.testCount ?? 0;
|
||||
const afterTests = after.totals?.testCount ?? 0;
|
||||
if (afterTests < beforeTests) {
|
||||
console.error(`Test count decreased from ${beforeTests} to ${afterTests}; refusing coverage-reducing patch.`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Test count preserved: ${beforeTests} -> ${afterTests}.`);
|
||||
NODE
|
||||
|
||||
- name: Check changed lanes
|
||||
if: steps.gate.outputs.run_agent == 'true' && steps.patch.outputs.has_changes == 'true'
|
||||
run: pnpm check:changed
|
||||
|
||||
- name: Commit test performance updates
|
||||
if: steps.gate.outputs.run_agent == 'true' && steps.patch.outputs.has_changes == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
TARGET_BRANCH: main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "No test performance changes."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "openclaw-test-performance-agent[bot]"
|
||||
git config user.email "openclaw-test-performance-agent[bot]@users.noreply.github.com"
|
||||
git add apps extensions packages scripts src Swabble test ui
|
||||
git commit --no-verify -m "test: optimize slow tests"
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if ! git fetch --no-tags origin "${TARGET_BRANCH}"; then
|
||||
echo "Fetch attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
continue
|
||||
fi
|
||||
if git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" HEAD:"${TARGET_BRANCH}"; then
|
||||
exit 0
|
||||
fi
|
||||
remote_main="$(git rev-parse "origin/${TARGET_BRANCH}")"
|
||||
if [ "$remote_main" != "$(git rev-parse HEAD^)" ]; then
|
||||
echo "main advanced; rebasing test performance update onto ${remote_main}."
|
||||
if ! git rebase "origin/${TARGET_BRANCH}"; then
|
||||
echo "Test performance update no longer applies cleanly; skipping stale update."
|
||||
git rebase --abort || true
|
||||
exit 0
|
||||
fi
|
||||
pnpm check:changed
|
||||
fi
|
||||
echo "Test performance update attempt ${attempt} failed; retrying."
|
||||
sleep $((attempt * 2))
|
||||
done
|
||||
|
||||
echo "Failed to push test performance updates after retries." >&2
|
||||
exit 1
|
||||
|
||||
- name: Upload test performance artifacts
|
||||
if: steps.gate.outputs.run_agent == 'true' && always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-performance-agent-${{ github.run_id }}
|
||||
path: .artifacts/test-perf/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user