Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 211 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,142 @@ jobs:
exit 1
fi

gateway-composed:
needs: repo-hygiene
runs-on: ubuntu-latest
timeout-minutes: 45
outputs:
sha256: ${{ steps.package.outputs.sha256 }}
steps:
- name: Fail if repo hygiene failed
if: ${{ needs.repo-hygiene.result != 'success' }}
shell: bash
run: |
echo "repo-hygiene failed; see the repo-hygiene job output." >&2
exit 1

- name: Checkout frozen OpenClaw main source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
repository: openclaw/openclaw
ref: b9a28cde42872cfe13051ddd8f1667e34bf59b17
fetch-depth: 0

- name: Setup Node 24
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24

- name: Activate pinned pnpm
shell: bash
run: |
corepack enable
corepack prepare pnpm@11.2.2 --activate

- name: Compose main with exact PR 110382 and PR 111956 patches
shell: bash
run: |
set -euo pipefail
base_sha=b9a28cde42872cfe13051ddd8f1667e34bf59b17
wizard_base=b9a28cde42872cfe13051ddd8f1667e34bf59b17
wizard_head=4ef65f07940a601a8bf7ef18fae72270f4843ccb
package_base=b9a28cde42872cfe13051ddd8f1667e34bf59b17
package_head=070527ddfe32f0156a6258a638e226f731458ecd
fork_repository=https://github.com/TheAngryPit/openclaw.git
wizard_fetch_ref=refs/heads/codex/fix-gateway-wizard-rpc-exits
package_fetch_ref=refs/heads/codex/windows-package-runner-tree-timeout
wizard_paths="$RUNNER_TEMP/pr110382-expected-paths.txt"
package_paths="$RUNNER_TEMP/pr111956-expected-paths.txt"
combined_paths="$RUNNER_TEMP/combined-expected-paths.txt"
staged_paths="$RUNNER_TEMP/combined-staged-paths.txt"
wizard_patch="$RUNNER_TEMP/pr110382.patch"
package_patch="$RUNNER_TEMP/pr111956.patch"

cat >"$wizard_paths" <<'EOF'
apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift
packages/gateway-protocol/src/schema/wizard.ts
src/gateway/gateway.test.ts
src/gateway/server-methods/wizard.test.ts
src/gateway/server-methods/wizard.ts
src/gateway/server-wizard-sessions.test.ts
src/gateway/server-wizard-sessions.ts
src/wizard/session.ts
src/wizard/setup.finalize.test.ts
src/wizard/setup.finalize.ts
EOF
cat >"$package_paths" <<'EOF'
scripts/package-openclaw-for-docker.d.mts
scripts/package-openclaw-for-docker.mjs
test/e2e/qa-lab/runtime/package-openclaw-for-docker.e2e.test.ts
EOF
cat "$wizard_paths" "$package_paths" | sort >"$combined_paths"

test "$(git rev-parse HEAD)" = "$base_sha"
# Fetch immutable reviewed commits from the operator fork. The upstream
# PR 111956 object was stale at freeze time, so it is not an authority.
git fetch --no-tags "$fork_repository" \
"+${wizard_fetch_ref}:refs/remotes/fork/pr-110382" \
"+${package_fetch_ref}:refs/remotes/fork/pr-111956"
test "$(git rev-parse refs/remotes/fork/pr-110382)" = "$wizard_head"
test "$(git rev-parse refs/remotes/fork/pr-111956)" = "$package_head"
git merge-base --is-ancestor "$wizard_base" "$wizard_head"
git merge-base --is-ancestor "$package_base" "$package_head"
git diff --name-only "$wizard_base" "$wizard_head" | sort | diff -u "$wizard_paths" -
git diff --name-only "$package_base" "$package_head" | sort | diff -u "$package_paths" -
git diff --binary "$wizard_base" "$wizard_head" -- $(cat "$wizard_paths") >"$wizard_patch"
git diff --binary "$package_base" "$package_head" -- $(cat "$package_paths") >"$package_patch"
test -s "$wizard_patch"
test -s "$package_patch"
sha256sum "$wizard_patch" >"$RUNNER_TEMP/pr110382-patch.SHA256SUM"
sha256sum "$package_patch" >"$RUNNER_TEMP/pr111956-patch.SHA256SUM"
git apply --index "$wizard_patch"
git apply --index "$package_patch"
git diff --cached --name-only | sort >"$staged_paths"
diff -u "$combined_paths" "$staged_paths"
git diff --cached --check

- name: Build immutable composed gateway package
id: package
shell: bash
run: |
set -euo pipefail
test "$(node -p "require('./package.json').version")" = "2026.7.2"
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
node scripts/package-openclaw-for-docker.mjs \
--allow-unreleased-changelog \
--source-dir . \
--output-dir "$RUNNER_TEMP/gateway-composed" \
--output-name openclaw-2026.7.2-main-pr110382-pr111956.tgz
tarball="$RUNNER_TEMP/gateway-composed/openclaw-2026.7.2-main-pr110382-pr111956.tgz"
sha256="$(sha256sum "$tarball" | awk '{print $1}')"
packaged_version="$(tar -xOf "$tarball" package/package.json | node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>process.stdout.write(JSON.parse(s).version))")"
test "$packaged_version" = "2026.7.2"
wizard_patch_sha256="$(awk '{print $1}' "$RUNNER_TEMP/pr110382-patch.SHA256SUM")"
package_patch_sha256="$(awk '{print $1}' "$RUNNER_TEMP/pr111956-patch.SHA256SUM")"
test "${#sha256}" -eq 64
test "${#wizard_patch_sha256}" -eq 64
test "${#package_patch_sha256}" -eq 64
printf '%s %s\n' "$sha256" "$(basename "$tarball")" >"$RUNNER_TEMP/gateway-composed/SHA256SUMS"
printf '{"source_ref":"main","source_sha":"%s","fixes":[{"pr":110382,"base":"%s","head":"%s","fetch_repository":"TheAngryPit/openclaw","fetch_ref":"codex/fix-gateway-wizard-rpc-exits","patch_sha256":"%s"},{"pr":111956,"base":"%s","head":"%s","fetch_repository":"TheAngryPit/openclaw","fetch_ref":"codex/windows-package-runner-tree-timeout","upstream_pr_object_state_at_freeze":"stale","patch_sha256":"%s"}],"changed_paths":["apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift","packages/gateway-protocol/src/schema/wizard.ts","scripts/package-openclaw-for-docker.d.mts","scripts/package-openclaw-for-docker.mjs","src/gateway/gateway.test.ts","src/gateway/server-methods/wizard.test.ts","src/gateway/server-methods/wizard.ts","src/gateway/server-wizard-sessions.test.ts","src/gateway/server-wizard-sessions.ts","src/wizard/session.ts","src/wizard/setup.finalize.test.ts","src/wizard/setup.finalize.ts","test/e2e/qa-lab/runtime/package-openclaw-for-docker.e2e.test.ts"],"package_version":"%s","package_sha256":"%s"}\n' \
b9a28cde42872cfe13051ddd8f1667e34bf59b17 \
b9a28cde42872cfe13051ddd8f1667e34bf59b17 \
4ef65f07940a601a8bf7ef18fae72270f4843ccb \
"$wizard_patch_sha256" \
b9a28cde42872cfe13051ddd8f1667e34bf59b17 \
070527ddfe32f0156a6258a638e226f731458ecd \
"$package_patch_sha256" \
"$packaged_version" \
"$sha256" >"$RUNNER_TEMP/gateway-composed/provenance.json"
echo "sha256=$sha256" >>"$GITHUB_OUTPUT"

- name: Upload composed gateway package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: openclaw-main-pr110382-pr111956-composed
path: ${{ runner.temp }}/gateway-composed/
if-no-files-found: error
retention-days: 7

test:
needs: repo-hygiene
if: ${{ !cancelled() }}
Expand Down Expand Up @@ -338,29 +474,50 @@ jobs:
majorMinorPatch: ${{ steps.gitversion.outputs.majorMinorPatch }}

e2etests:
needs: repo-hygiene
if: ${{ !cancelled() }}
needs: [repo-hygiene, gateway-composed]
if: ${{ needs.repo-hygiene.result == 'success' && needs.gateway-composed.result == 'success' }}
runs-on: windows-latest
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
matrix:
include:
- name: setup-connect
- name: lkg-setup-connect
gateway_source: lkg
gateway_version: ""
scenario: setup-connect
timeout_minutes: 45
filter: "FullyQualifiedName~OpenClaw.E2ETests.Setup.SetupAndConnectTests|FullyQualifiedName~OpenClaw.E2ETests.Setup.MxcSetupAndConnectTests"
- name: official-beta-setup-connect
gateway_source: official
gateway_version: 2026.7.2-beta.3
scenario: setup-connect
timeout_minutes: 45
filter: "FullyQualifiedName~OpenClaw.E2ETests.Setup.SetupAndConnectTests|FullyQualifiedName~OpenClaw.E2ETests.Setup.MxcSetupAndConnectTests"
- name: revocation-recovery
- name: composed-setup-connect
gateway_source: composed
gateway_version: ""
scenario: setup-connect
timeout_minutes: 45
filter: "FullyQualifiedName~OpenClaw.E2ETests.Setup.SetupAndConnectTests|FullyQualifiedName~OpenClaw.E2ETests.Setup.MxcSetupAndConnectTests"
- name: composed-revocation-recovery
gateway_source: composed
gateway_version: ""
scenario: revocation-recovery
timeout_minutes: 25
filter: FullyQualifiedName~OpenClaw.E2ETests.Setup.RevocationAndRecoveryTests
- name: network-recovery
- name: composed-network-recovery
gateway_source: composed
gateway_version: ""
scenario: network-recovery
timeout_minutes: 25
filter: FullyQualifiedName~OpenClaw.E2ETests.Setup.NetworkRecoveryTests
steps:
- name: Fail if repo hygiene failed
if: ${{ needs.repo-hygiene.result != 'success' }}
if: ${{ needs.repo-hygiene.result != 'success' || needs.gateway-composed.result != 'success' }}
shell: pwsh
run: |
Write-Error "repo-hygiene failed; see the repo-hygiene job output."
Write-Error "A prerequisite failed; see repo-hygiene and gateway-composed job output."
exit 1

- uses: actions/checkout@v7
Expand Down Expand Up @@ -395,9 +552,46 @@ jobs:
- name: Build E2E Tests
run: dotnet build tests/OpenClaw.E2ETests -c Debug -r win-x64

- name: Run gateway package selector tests
if: ${{ matrix.name == 'official-beta-setup-connect' }}
shell: pwsh
run: |
dotnet test tests/OpenClaw.E2ETests `
--no-build `
-c Debug `
-r win-x64 `
--verbosity normal `
--filter "FullyQualifiedName~OpenClaw.E2ETests.Setup.GatewayE2EPackageSpecTests"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Download composed gateway package
if: ${{ matrix.gateway_source == 'composed' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: openclaw-main-pr110382-pr111956-composed
path: TestResults/GatewayComposed

- name: Start disposable WSL package host
if: ${{ matrix.gateway_source == 'composed' }}
id: gateway_package_host
shell: pwsh
run: |
.\scripts\Start-E2EGatewayPackageHost.ps1 `
-PackagePath "TestResults\GatewayComposed\openclaw-2026.7.2-main-pr110382-pr111956.tgz" `
-ExpectedSha256 "${{ needs.gateway-composed.outputs.sha256 }}" `
-DistroName "OpenClawE2EPackageHost-${{ matrix.name }}" `
-InstallLocation "${{ runner.temp }}\OpenClawE2EPackageHost-${{ matrix.name }}" `
-OwnershipToken "${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.name }}" `
-GitHubOutput $env:GITHUB_OUTPUT

- name: Run E2E Tests (${{ matrix.name }})
env:
OPENCLAW_RUN_E2E: 1
OPENCLAW_E2E_GATEWAY_SOURCE: ${{ matrix.gateway_source }}
OPENCLAW_E2E_GATEWAY_VERSION: ${{ matrix.gateway_version }}
OPENCLAW_E2E_GATEWAY_PACKAGE_SPEC: ${{ matrix.gateway_source == 'composed' && steps.gateway_package_host.outputs.package_spec || '' }}
OPENCLAW_E2E_GATEWAY_PACKAGE_SHA256: ${{ matrix.gateway_source == 'composed' && steps.gateway_package_host.outputs.package_sha256 || '' }}
OPENCLAW_E2E_GATEWAY_PACKAGE_HOST_DISTRO: ${{ matrix.gateway_source == 'composed' && steps.gateway_package_host.outputs.distro_name || '' }}
shell: pwsh
run: |
dotnet test tests/OpenClaw.E2ETests `
Expand All @@ -417,7 +611,7 @@ jobs:
Write-Error "E2E shard '${{ matrix.name }}' executed zero tests. Check OPENCLAW_RUN_E2E gating/filter before merging."
exit 1
}
if ("${{ matrix.name }}" -eq "setup-connect") {
if ("${{ matrix.scenario }}" -eq "setup-connect") {
$mxcProofNames = @(
"RealGateway_SystemRun_ExecutesThroughWindowsNodeMxcSandbox",
"RealGateway_SystemRun_BlocksWritesToTrayDataDirectoryInMxcSandbox"
Expand Down Expand Up @@ -448,6 +642,15 @@ jobs:
}
}

- name: Stop disposable WSL package host
if: ${{ always() && matrix.gateway_source == 'composed' }}
shell: pwsh
run: |
.\scripts\Stop-E2EGatewayPackageHost.ps1 `
-DistroName "OpenClawE2EPackageHost-${{ matrix.name }}" `
-InstallLocation "${{ runner.temp }}\OpenClawE2EPackageHost-${{ matrix.name }}" `
-OwnershipToken "${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.name }}"

- name: Upload E2E Test Results & Logs
if: always()
uses: actions/upload-artifact@v7
Expand Down
62 changes: 36 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,40 +251,50 @@ Packaged installs declare camera, microphone, and location capabilities. Windows
openclaw devices list # Find your Windows device
openclaw devices approve <id> # Approve it
```
4. **Configure gateway allowCommands** - Add the commands you want to allow under `gateway.nodes` in `~/.openclaw/openclaw.json`:
4. **Configure the gateway node-command allowlist** - Match the key to the Gateway version reported by `openclaw --version`. Current/frozen packages use `gateway.nodes.commands.allow`; the supported legacy versions `2026.6.11` and `2026.7.2-beta.3` use `gateway.nodes.allowCommands`.

Current/frozen schema:
```json
{
"gateway": {
"nodes": {
"allowCommands": [
"system.notify",
"system.run",
"system.run.prepare",
"system.which",
"system.execApprovals.get",
"system.execApprovals.set",
"canvas.present",
"canvas.hide",
"canvas.navigate",
"canvas.eval",
"canvas.snapshot",
"canvas.a2ui.push",
"canvas.a2ui.pushJSONL",
"canvas.a2ui.reset",
"screen.snapshot",
"camera.list",
"camera.snap",
"camera.clip",
"location.get",
"device.info",
"device.status",
"tts.speak"
]
"commands": {
"allow": [
"system.notify",
"system.run",
"system.run.prepare",
"system.which",
"system.execApprovals.get",
"system.execApprovals.set",
"canvas.present",
"canvas.hide",
"canvas.navigate",
"canvas.eval",
"canvas.snapshot",
"canvas.a2ui.push",
"canvas.a2ui.pushJSONL",
"canvas.a2ui.reset",
"screen.snapshot",
"camera.list",
"camera.snap",
"camera.clip",
"location.get",
"device.info",
"device.status",
"tts.speak"
]
}
}
}
}
```
> ⚠️ **Important**: The gateway has a server-side allowlist. Commands must be listed explicitly - wildcards like `canvas.*` don't work! Privacy-sensitive commands such as `screen.record` and agent-driven audio playback via `tts.speak` should only be added to `allowCommands` when you explicitly want to allow them.

For `2026.6.11` or `2026.7.2-beta.3`, preserve the complete array above but
move it directly to `gateway.nodes.allowCommands`: remove the `commands`
wrapper and rename its `allow` property to `allowCommands`. Do not shorten
the array when applying this legacy key transformation.

> ⚠️ **Important**: The gateway has a server-side allowlist. Commands must be listed explicitly - wildcards like `canvas.*` don't work! Privacy-sensitive commands such as `screen.record` and agent-driven audio playback via `tts.speak` should only be added to the version-appropriate allowlist when you explicitly want to allow them.

5. **Test it** from your Mac/gateway:
```bash
Expand Down
2 changes: 2 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ leading and trailing pipe. Columns, in order:
| wsl-posix-quoting | authoritative | OpenClaw.SetupEngine/SetupSteps.cs | ad hoc ShellEscape with divergent wrap semantics | WslShellQuoting | - | WSL command lines use POSIX single-quote quoting via WslShellQuoting not cmd/PowerShell quoting | WslShellQuotingTests.QuotePosixSingleQuote_WrapsAndEscapesEmbeddedQuote | behavioral | when no code builds WSL command lines outside WslShellQuoting |
| setup-shellescape-closed | closed | src/OpenClaw.SetupEngine/SetupSteps.cs | private ShellEscape helpers with divergent wrap semantics | WslShellQuoting | - | SetupSteps builds WSL command lines only via WslShellQuoting; no local ShellEscape helper | SetupStepsShellEscapeClosureTests.SetupSteps_DoesNotReintroduce_PrivateShellEscape | source-shape | when SetupSteps.cs no longer builds any WSL command strings |
| wsl-distro-install-path | authoritative | OpenClaw.SetupEngine/SetupSteps.cs | inline Path.Combine wsl distro install-path derivation | DistroInstallPathPolicy | - | new installs use the strict supported name grammar; teardown accepts only unambiguous single-segment names whose canonical path is an immediate child of LocalDataDir\wsl with no aliases, case or Unicode collisions, or reparse points at the root or child | SetupStepsTests.DistroInstallPathPolicy_ResolvesImmediateChild | behavioral | - |
| gateway-version-alignment | authoritative | src/OpenClaw.Tray.WinUI/App.xaml.cs | local WSL version probe, pinned update transaction, and recovery result policy | OpenClaw.Connection/GatewayVersionAlignmentCoordinator | App composes the service and presents confirmation/results only | normal update never unregisters/imports; exact package version, full rollback point, and Gateway/Node/pairing health gate completion | GatewayVersionAlignmentCoordinatorTests.UpdateAsync_CreatesOfflineVerifiedPointThenUpdatesAndCleansOnlyAfterHealth | behavioral | - |
| gateway-rollback-points | authoritative | none | offline VHD export, manifest receipts, retention, verification, and explicit emergency restore | OpenClaw.Connection/GatewayRollbackPointManager | SettingsPage owns selection and confirmation UI only | immutable verified VHD is retained outside the live distro; unregister/import-in-place is restore-only; direct ext4.vhdx replacement is forbidden | GatewayVersionAlignmentCoordinatorTests.RestoreAsync_RecreatesOnlyRegistrationThenProvesVersionIdentityAndSynchronization | behavioral | - |
| app-window-manager | planned | src/OpenClaw.Tray.WinUI/App.xaml.cs | window creation/show/hide/shutdown | IWindowManager | composition/delegation only | startup/shutdown ordering deterministic; disposed once | none | review-only | extracted in Phase 3 |
| app-tray-controller | planned | src/OpenClaw.Tray.WinUI/App.xaml.cs | tray icon/menu/action routing | ITrayController | composition/delegation only | tray actions route unchanged | none | review-only | extracted in Phase 3 |
| app-activation-router | planned | src/OpenClaw.Tray.WinUI/App.xaml.cs | deep-link/toast/single-instance activation | IActivationRouter | composition/delegation only | activation routes land on the same UI/actions; current-user pipe security preserved | none | review-only | extracted in Phase 3 |
Expand Down
Loading
Loading