Skip to content
Merged
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
24 changes: 24 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@ downloading one with `gh`. MSIX composition requires Visual Studio Build Tools
with the Desktop development with C++ workload and the Windows SDK. Build x64
and ARM64 separately.

For the development inner loop, register a Developer Mode layout instead of
building an MSIX:

```powershell
.\scripts\Deploy-LocalPackage.ps1
```

`Deploy-LocalPackage.ps1` publishes the NativeAOT launcher, assembles a layout
under `artifacts\local-package`, registers it with `Add-AppxPackage -Register`,
and runs `clawctl setup`. It never builds, signs, or installs an MSIX, and it
requires no changes to the packaging scripts or project files. It is idempotent
and skips work when nothing changed, so keep its up-to-date check honest: the
fingerprint hashes the launcher rather than trusting timestamps, because
publish can refresh timestamps with no source change.

Loose registration and MSIX installation are mutually exclusive for one package
identity, and Windows cannot preserve packaged app data across that switch, so
the script refuses by default and requires `-ReplaceExistingInstall`. The
registered package reads its files from the repository, so treat
`artifacts\local-package` and the checkout as live inputs, not scratch output.
Its scenario tests inject every GitHub, publish, certificate-free registration,
and deployment operation; no test may register, remove, or modify a real
package.

## Architecture

- `OpenClaw.Gateway.Launcher` is a .NET 10 NativeAOT executable packaged as
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/gateway-msix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ jobs:
run: >
.\scripts\Test-WorkflowPackageVersion.Tests.ps1

- name: Test local package deployment
shell: pwsh
run: >
.\scripts\Test-Deploy-LocalPackage.Tests.ps1

- name: Test MSIX bundle build
shell: pwsh
run: >
Expand Down
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,63 @@ an unsigned NativeAOT MSIX.
local payload directory. `-NodeArchivePath` can supply an already-downloaded
archive, but its version and architecture must match the payload metadata.

### Running a local development build

To go from a clean checkout to a registered, runnable package:

```powershell
.\scripts\Deploy-LocalPackage.ps1
```

This is the development inner loop. It does not build, sign, or install an
MSIX. It acquires the payload and the bundled Node.js runtime, publishes the
NativeAOT launcher, assembles a Developer Mode layout under
`artifacts\local-package`, registers it with `Add-AppxPackage -Register`, and
runs `clawctl setup` so `openclaw` is immediately usable.

The command is idempotent: re-running with nothing changed reports that the
package is already up to date and does nothing, and re-running after a source
or payload change rebuilds only what changed. The expanded application is
linked into the layout rather than copied, so repeat runs neither re-download
nor duplicate hundreds of megabytes.

| Option | Behavior |
| --- | --- |
| `-RefreshPayload` | Download the payload again; the previous one is kept until the new one registers successfully |
| `-PayloadRunId <id>` | Use a specific successful workflow run, reusing a matching cached payload |
| `-PayloadDirectory <path>` | Read a prepared payload directly, with no GitHub access and no modification; pass it on every run |
| `-Architecture x64` / `arm64` | Select the architecture; it must be runnable on this device |
| `-ReplaceExistingInstall` | Remove a conflicting MSIX-installed package first (see below) |
| `-SkipSetup` | Register without extracting the Node.js runtime |
| `-Force` | Re-register even when nothing changed |
| `-Unregister` | Remove the local registration, preserving app data and caches |

**Requires Developer Mode**, which the script checks before doing any work.

**It cannot coexist with an MSIX-installed `OpenClaw.Gateway`.** Windows
refuses to replace a packaged install with a local layout, and it cannot
preserve that package's app data across the switch, so the script stops and
explains rather than removing anything implicitly. Pass
`-ReplaceExistingInstall` to accept that trade.

**Run `-Unregister` before installing a released package.** Windows will not
replace a loose registration with a packaged install: `Add-AppxPackage` fails
with `0x80073CFB`, reporting that an unpackaged version is already installed
and a packaged version cannot replace it. This is the same mutual exclusion as
above, in the other direction, and it applies regardless of version. Unregister
first, then install the release:

```powershell
.\scripts\Deploy-LocalPackage.ps1 -Unregister
Add-AppxPackage -Path .\OpenClawGateway-0.0.0.0-x64.msix
```

**The registered package reads its files from the repository.** Deleting
`artifacts\local-package`, moving the checkout, or deleting the worktree breaks
the registration until the command runs again; `-Unregister` first if you plan
to remove the checkout. Local builds are unsigned development artifacts and are
never official-signing inputs.

Normal pull-request and push workflows publish unsigned packages for
validation. Manual runs support three signing modes:

Expand Down
107 changes: 107 additions & 0 deletions scripts/Deploy-LocalPackage.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<#
.SYNOPSIS
Builds and registers a local development OpenClaw Gateway package, ready to run.
.DESCRIPTION
Takes a clean checkout to a registered, runnable OpenClaw.Gateway package without
producing, signing, or installing an MSIX. It acquires the OpenClaw payload and
the bundled Node.js runtime, publishes the NativeAOT launcher, assembles a
Developer Mode layout, and registers it with Add-AppxPackage -Register.

The command is idempotent. Re-running with nothing changed reports that the
package is already up to date and does nothing; re-running after a source or
payload change rebuilds only what changed and re-registers. Downloads are cached
under artifacts\local-package, and the expanded application is linked rather
than copied, so repeat runs do not re-fetch or duplicate hundreds of megabytes.

Requires Developer Mode. The registered package reads its files from the
repository, so deleting artifacts\local-package or the checkout breaks it until
the command runs again. This is a development build and is not an
official-signing input; use Build-LocalMSIX.ps1 for a verified unsigned MSIX.
.PARAMETER Architecture
Build and register x64 (default) or arm64. The selected architecture must be
runnable on this device.
.PARAMETER PayloadDirectory
Use an existing payload root containing app and payload-metadata.json. The
directory is read directly and never modified, and no GitHub access is needed.
Pass it on every run; it is not remembered.
.PARAMETER PayloadRunId
Use a specific successful workflow run instead of the latest one on main. A
matching cached payload is reused rather than downloaded again.
.PARAMETER RefreshPayload
Download the payload again instead of reusing the cache. The previous payload
is kept until the new one is registered successfully.
.PARAMETER ReplaceExistingInstall
Take over an existing install this checkout does not own: an MSIX-installed
OpenClaw.Gateway, or a local registration from another checkout. Windows cannot
replace a packaged install with a local layout and cannot preserve its app data
across that switch, so this is never done implicitly.
.PARAMETER Force
Re-register even when nothing changed.
.PARAMETER SkipSetup
Skip the final `clawctl setup` that extracts the bundled Node.js runtime. The
package is registered but not runnable until setup is run once.
.PARAMETER Unregister
Remove the local development registration and exit. Cached payloads and
runtimes are kept, and the package's app data is preserved. Run this before
installing a released package: Windows will not replace a loose registration
with a packaged install, regardless of version.
.EXAMPLE
.\scripts\Deploy-LocalPackage.ps1
Clean checkout to a registered, runnable package; later runs reuse the cache.
.EXAMPLE
.\scripts\Deploy-LocalPackage.ps1 -RefreshPayload
Pick up a newer OpenClaw payload from the latest successful main workflow run.
.EXAMPLE
.\scripts\Deploy-LocalPackage.ps1 -PayloadDirectory E:\payloads\x64
Register from a prepared payload without contacting GitHub.
.EXAMPLE
.\scripts\Deploy-LocalPackage.ps1 -Unregister
Remove the local registration.
#>
[CmdletBinding(DefaultParameterSetName = 'Deploy')]
param(
[ValidateSet('x64', 'arm64')]
[string]$Architecture = 'x64',

[Parameter(ParameterSetName = 'Deploy')]
[string]$PayloadDirectory,

[Parameter(ParameterSetName = 'Deploy')]
[long]$PayloadRunId,

[Parameter(ParameterSetName = 'Deploy')]
[switch]$RefreshPayload,

[Parameter(ParameterSetName = 'Deploy')]
[switch]$ReplaceExistingInstall,

[Parameter(ParameterSetName = 'Deploy')]
[switch]$Force,

[Parameter(ParameterSetName = 'Deploy')]
[switch]$SkipSetup,

[Parameter(Mandatory, ParameterSetName = 'Unregister')]
[switch]$Unregister
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$repositoryRoot = Split-Path $PSScriptRoot -Parent
Import-Module (Join-Path $PSScriptRoot 'LocalPackage.psm1') -Force

if ($Unregister) {
Remove-LocalPackageRegistration -RepositoryRoot $repositoryRoot -Architecture $Architecture
return
}

Invoke-LocalPackageDeployment `
-RepositoryRoot $repositoryRoot `
-Architecture $Architecture `
-PayloadDirectory $PayloadDirectory `
-PayloadRunId $PayloadRunId `
-RefreshPayload:$RefreshPayload `
-ReplaceExistingInstall:$ReplaceExistingInstall `
-Force:$Force `
-SkipSetup:$SkipSetup |
Out-Null
Loading