Skip to content

Add themed Windows Launcher status plugin - #2

Closed
ChazGo wants to merge 40 commits into
mainfrom
chazgo-themed-windows-launcher-plugin
Closed

ChazGo wants to merge 40 commits into
mainfrom
chazgo-themed-windows-launcher-plugin

Conversation

@ChazGo

@ChazGo ChazGo commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Why is this change being made?

The Windows package needs a packaging-owned Control UI surface for launcher diagnostics because Gateway isolation mode is selected and reported by the Windows launcher, not by the generic OpenClaw runtime.

The plugin consumes the generic selected-theme forwarding merged in openclaw/openclaw#145409 at commit f65ecca89667b8a55d9f88d76c487f0a0ab11da8. Full selected-theme cohesion requires that core commit or newer. Older compatible hosts and direct page opens retain the browser or OS light/dark fallback.

This is a separate alternative to openclaw#28. It does not modify or reuse that pull request.

What changed?

  • Added the bundled packaging plugin with internal ID, path, and package name gateway-isolation.
  • Presented the user-facing sidebar, tab, document title, and page heading as Windows Launcher.
  • Presented the launcher diagnostic row as Gateway Isolation while retaining the existing CLI command and Copy control.
  • Consumed validated openclaw:widget-theme messages only from window.parent, including semantic colors, text and border strength, focus, fonts, and standard and pill radii.
  • Preserved the safe prefers-color-scheme fallback and fail-closed invalid launcher modes.
  • Kept the authenticated route read-only and capability-minimal, with sandbox="allow-scripts" and no allow-same-origin.
  • Provisioned the same plugin files through the x64 and ARM64 payload paths and pinned automatic package builds to the merged core compatibility commit.

How was the change tested?

  • 23/23 plugin Node contract tests.
  • 84/84 launcher xUnit tests.
  • Release managed build and static analysis with zero warnings.
  • NativeAOT x64 publish.
  • Plugin packaging smoke and workflow, signing, bundle, package-version, and Git-hook policy checks.
  • Exact merged-core x64 expanded-layout browser proof with one authenticated iframe request, no iframe reload across live theme switches, semantic token checks, sandbox checks, and read-only route checks.
  • Local ARM64 production payload installation remains for CI because a third-party koffi ARM64 install attempted a local native rebuild on this machine. The repository's synthetic x64/ARM64 payload coverage remains part of the validation path.

This proof is expanded-layout browser validation, not installed MSIX validation.

Default Claw themes

Windows Launcher using the Claw dark theme

Windows Launcher using the Claw light theme

Imported custom themes

Windows Launcher using an imported custom dark theme

Windows Launcher using an imported custom light theme

paulcam206 and others added 30 commits September 10, 2026 15:38
Enable the analyzers that ship with the pinned .NET SDK and add a root
.editorconfig that defines formatting, naming, and IDE style explicitly.
Every rule reports as a warning at this layer so the diagnostic backlog is
visible without blocking the build.

Add scripts/Test-DotNetQuality.ps1 as the single quality entry point shared
by local development, CI, and the future pre-push hook, and invoke it from
the Windows test job. Add CONTRIBUTING.md and a pull request template, which
the repository previously lacked.

GenerateDocumentationFile is required for build-time IDE0005, so CS1591 is
suppressed rather than satisfied with low-value XML comments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11
Apply the outstanding whitespace and style fixes, then promote the IDE style
and naming rules from warning to error so CI rejects future drift.

The code changes are behavior preserving: indentation in ProgramTests, two
collection expressions, and the diagnostic log's lock object moving from
`object` to `System.Threading.Lock`. The lock field is only ever used in
`lock` statements, so the new type introduces no Monitor-based behavior
change.

Also refine the style policy so it only enforces rules that add value. `var`
versus explicit type, throw expressions, conditional expressions over guard
clauses, and IDE0058 discards are left to the author, because forcing them
produced worse code in this codebase rather than better.

Test-DotNetQuality.ps1 now also verifies whitespace and style. It remains
check-only and never rewrites source, including in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11
* improve: harden native library loading

All ten P/Invokes in the launcher target kernel32.dll. Without an explicit
search path the loader also probes the application and current directories,
so a planted kernel32.dll next to openclaw.exe could be loaded instead of the
system one. Restrict DLL resolution to System32 for the whole assembly.

Also stop marshalling the CreateProcessW command line through a StringBuilder.
CreateProcessW may write to that buffer, so it now receives an explicitly
null-terminated char array sized from the built command line. The buffer is
never read back.

Promote CA5392 and CA1838 to errors now that both are clear, and add a
regression test that launches a real child process with an argument
containing spaces and embedded quotes to prove the command line still
arrives intact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11

* fix: keep child command text free of fixture paths

The real-child argument test interpolated the fixture output path into a
single-quoted PowerShell literal. A temporary root containing an apostrophe
(for example C:\Users\O'Connor) terminated that literal early and turned the
remaining path into PowerShell syntax, so the test failed for a reason
unrelated to argument marshalling.

Pass the output path to the child as environment data and keep the command
text fixed. The spaced and embedded-quote payload still travels on the command
line, so the marshalling assertion is unchanged, and the case is now covered
for both a plain fixture subdirectory and one containing spaces and an
apostrophe.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11
Narrow the launcher's implementation types to internal and make every
string comparison in the test suite explicit and culture-independent, then
promote the contract and globalization analyzer families to errors.

Internalizing the seven implementation-only types removes the ambiguity
about what this executable's supported surface is. Because these types are
no longer externally visible, the six CA1062 public-argument-validation
findings disappear without adding guard clauses that would have become
dead weight. The test project keeps full access through the existing
InternalsVisibleTo entry.

The CA1307 findings were all Assert.Contains calls matching literal English
fragments of diagnostic and exception messages. StringComparison.Ordinal is
the semantically correct choice: these assertions want exact, invariant
substring matching, not culture-sensitive collation.

CA1062, CA1307, CA1308, and CA1515 are now errors. CA1308 and CA1062 have
zero findings and act as forward guards. CA1515 is disabled for the test
project because xUnit 2.x only discovers public test classes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11
Make every await in the launcher explicit about continuation scheduling and
replace the remaining synchronous calls inside async methods, then promote
the async analyzer family to errors.

The launcher is a console executable with no SynchronizationContext, so
ConfigureAwait(false) is a no-op at runtime today. It is applied anyway
because the rule protects future code: if this assembly is ever consumed
from a context-capturing host, or a UI-hosted setup surface is added, the
absence of an explicit choice becomes a deadlock risk rather than a style
question. Making the intent explicit at every await costs nothing now and
removes an entire defect class later.

CA1849 flagged two genuine synchronous calls on async paths: the clawctl
--version write to the output TextWriter, and File.ReadAllText in the
interop argument-delivery test. Both now use their asynchronous overloads,
and the test threads its existing timeout token through the read so a hung
file operation fails the test rather than hanging the run.

CA2007 does not apply to the test project; the SDK excludes it
automatically for projects that reference a test framework, so no
ConfigureAwait noise is added to test code.

Validated on this branch: static analysis gate clean, 53 tests pass, and
win-x64 NativeAOT publish succeeds with no IL, trim, or AOT warnings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11
Give every disposable in the test suite an explicit scope, document the one
deliberate broad catch, sharpen a private return type, and promote the
remaining lifetime and design analyzer families to errors. After this
change the branch builds with zero warnings.

The three CA2000 findings were StringWriter instances in tests that were
never disposed. They are now scoped with using declarations. This is small
but real: undisposed writers accumulate across a run and mask genuine
ownership mistakes in the same files later.

CA1031 flagged the last-chance catch in Main. That catch is correct and
deliberate. Every other catch in this assembly already uses an exception
filter to name the failures it expects; Main is the process boundary, and
narrowing it would trade a logged diagnostic, a readable error message, and
a deterministic exit code 1 for an unhandled-exception crash. The rule is
suppressed at that single method with that rationale rather than repository
wide, so any new broad catch elsewhere still fails the build.

CA1859 flagged FindPathCandidates returning IReadOnlyList<string> from a
private helper that always produces a List<string>. The interface added no
contract value across a private call and cost an interface dispatch on the
PATH scan; the concrete type is now returned directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11
* chore: enforce the complete .NET quality gate

Turn on TreatWarningsAsErrors now that the branch builds warning-free, and
add an opt-in pre-push hook so contributors can get the CI answer before
pushing.

The per-rule error entries the earlier layers added only cover diagnostics
somebody has already looked at. The gap they leave is everything nobody has
classified yet: a new compiler warning, a new analyzer from an SDK bump, a
rule that changes default severity. Those arrive as warnings and get
scrolled past. TreatWarningsAsErrors turns each one into a decision at the
moment it appears. The explicit .editorconfig severities stay as readable
policy history.

NuGet audit advisories are excluded from the gate. NU1901 through NU1904
report published vulnerabilities, so one can appear against a dependency
graph nobody touched and break main with no committed change and no fix
available inside the failing build. They remain visible as warnings and are
triaged as security work rather than treated as build breaks.

The hook is deliberately small and deliberately opt in. hooks/pre-push is
tracked, carries a marker identifying it as repository-owned, and runs only
Test-DotNetQuality.ps1, so it cannot drift from what CI does.
Install-GitHooks.ps1 copies it into the current clone and -Remove deletes
it. Neither direction touches global Git configuration or core.hooksPath;
installation stops if core.hooksPath is set rather than writing a hook Git
would silently ignore, refuses to overwrite a pre-push hook it did not
write, and removal only deletes a hook carrying the marker. Both are
idempotent.

The hook is a latency shortcut, not a policy boundary. It is local to one
clone and `git push --no-verify` skips it; required CI checks remain
authoritative. That is stated in the hook, the installer, and CONTRIBUTING
so nobody mistakes it for enforcement.

Test-GitHooks.Tests.ps1 covers install, repeated install, removal, repeated
removal, unmanaged-hook conflict in both directions, and the core.hooksPath
refusal. The pass and fail cases are end to end: they build a throwaway
repository with a local bare remote and a stub quality script, then perform
a real git push, proving Git invokes the hook and that its exit code
decides whether the push lands. Nothing runs against this clone. CI runs the
suite alongside the existing policy suites.

Validated on this branch: quality gate clean, 53 tests pass, all three
PowerShell suites pass, win-x64 NativeAOT publish succeeds with no IL, trim,
or AOT warnings, and an injected #warning fails the build as CS1030.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11

* fix: install the pre-push hook where Git looks for it

The installer composed its destination from git rev-parse --absolute-git-dir
plus 'hooks'. In a linked worktree that is the worktree's private directory,
which Git never consults for hooks, so installation reported success and no
push ever ran the quality gate.

Ask Git for the hooks directory instead, with rev-parse --git-path hooks, and
use that answer for both installation and removal. A clone has one hooks
directory shared by all of its worktrees, so say so in the installer output and
in CONTRIBUTING.

The hook suite now runs every existing case against both an ordinary clone and
a linked worktree. In the worktree layout the two work trees carry opposite
stub exit codes, so the real pushes prove Git ran the pushing worktree's
quality script. Two further cases cover the shared destination and clone-wide
removal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 331d9f1d-2f78-491c-8347-19969cb5ce11
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7cb16ef4-e347-4342-a67e-385a8721db1c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7cb16ef4-e347-4342-a67e-385a8721db1c
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a35bc4fd-e612-4be2-8166-7c100e8cf3ef
The signing script previously skipped both architecture directories when the artifact root was not CI-shaped, then reported success after creating and discarding a certificate. Validate the expected architecture directories before creating signing material so a no-op cannot look like a signed result.

Add a regression test that runs the script against an empty artifact root and verifies the failure message, non-zero exit, absence of output, and absence of a newly created publisher certificate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 97c31f3f-0c7b-43e8-b979-6418dcb7fedc
…openclaw#29)

* Parse clawctl arguments with System.CommandLine

Replace the hand-written clawctl parser and help renderer with a declarative
System.CommandLine tree. The `openclaw` entrypoint is unchanged and still
forwards its argument vector to the OpenClaw CLI without parsing it.

- Add ClawCtlCommandLine: a root command plus a `setup` subcommand, with Node
  prerequisite and install guidance in the command descriptions so help is
  generated from one source instead of hand-formatted.
- Delete ClawCtlCommand and its enum/parse-result/exact-token matcher rather
  than leaving a second dispatch model beside the library.
- Rewrite Program.RunControlAsync around the tree. Disable the library's
  default exception handler so operational failures keep reaching the host's
  diagnostic boundary, and leave ProcessTerminationTimeout null so it does not
  compete with the Node job object for process lifetime. The setup action's
  cancellation token now flows into the Node resolver.
- Replace the `Action<string> writeError` seam with an injected TextWriter and
  drop the direct Console.Error write from the control dispatcher.
- Report the launcher assembly version through a custom version action. The
  built-in one reports the entry assembly, which under a test or scenario host
  is not the launcher.
- Disable response-file expansion. A leading `@` is an unrecognized argument
  for clawctl and stays uninterpreted through `openclaw`. Completion is kept.

Invalid management input now exits 1, the library's parse-error code, instead
of the previous 2.

Add Test-NativeAotCli.Tests.ps1 and run it in CI. The xUnit suite runs under a
JIT test host, so it cannot observe the root command name that
System.CommandLine derives from native argv[0], and a successful AOT publish is
not execution evidence. The script publishes win-x64 NativeAOT into a temporary
directory it owns, runs the binary as clawctl.exe, and removes it afterwards.
Node-dependent scenarios stay in xUnit with an injected runtime so the gate does
not depend on the agent's installed Node version.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34

* Cover the completion directive and version precedence

The only completion test used the in-process GetCompletions API, which no
shell can reach. Cover the `[suggest]` directive through the real control
dispatcher instead, since that is the path a completion client actually calls.

Also record that `--version` now wins over trailing arguments. The old parser
rejected the combination; the library's version action clears parse errors, so
`clawctl --version bogus` prints the version and exits 0. That is stock
System.CommandLine behavior, not a local choice, but it is a visible change to
this CLI's contract and was previously untested.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34

* fix: exit the NativeAOT clawctl gate with its own status

The gate's final assertion runs clawctl with an argument that must be rejected,
so $LASTEXITCODE is 1 when the script ends. GitHub Actions exits a pwsh step
with that value, so the step failed even though every assertion passed. The job
printed "NativeAOT clawctl checks completed successfully." immediately before
reporting exit code 1.

Exit 0 explicitly after cleanup. Assertion failures throw and never reach that
line, so the gate still fails when it should: verified by injecting a pattern
that cannot match, which exits 1 with the offending output.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34

* Isolate the NativeAOT command-line gate from the user profile

The native gate published the production launcher and ran its `Main`.
`Main` creates the diagnostic log through
`HostDiagnosticLog.Create()`, which resolves
`%LOCALAPPDATA%\OpenClawGatewayMSIX` rather than anything relative to the
publish directory, so every gate run appended startup and exit records to
the developer's real log. Publishing into a temporary directory did not
redirect that, and green CI never revealed it.

Extract the startup path into `Program.RunAsync(args, HostStartup)` and
leave `Main` as the production adapter that supplies the real
collaborators. `HostStartup` carries the entrypoint, the diagnostic
factory, the base directory, the writers, and the existing Node and launch
seams, so startup can be driven with fixture-owned storage.

Replace the gate's subject with a NativeAOT scenario driver in
`tests/OpenClaw.Launcher.AotSmoke`. It runs the same `RunAsync` under the
`clawctl.exe` name with an explicit temporary log path, in-memory writers,
and delegates that cannot start a real process, so native coverage still
includes startup logging and the error boundary. The script now also runs
the driver under a wrong executable name and requires a nonzero exit, so
the alias check cannot pass vacuously.

Add JIT regressions for the same seam: diagnostics routed to a test path,
a handled operational failure reporting that path and exiting 1, a failed
diagnostic factory warning once and continuing, and `openclaw` forwarding
arguments verbatim while returning the child's exit code.

A read-only before/after observation of the normal unpackaged log across a
full gate run showed an unchanged length, timestamp, and SHA-256.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34

* Keep the NativeAOT scenario fixtures out of %TEMP%

CodeQL treats `Path.GetTempPath()` as a user-controlled source, because
`TMP` is an environment variable a caller controls. The scenario driver is
a plain console executable rather than a test-framework project, so it is
not classified as test code, and its fixture roots flowed into
`HostOptions.Parse` and the driver's own file writes as eight new
high-severity `cs/path-injection` alerts on the pull request.

Build the fixture roots under `AppContext.BaseDirectory` instead. The gate
script already publishes into a directory it owns and deletes, so the
scenarios stay isolated without reading an environment variable, and the
alerts have no source.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 79e90679-15f4-4fd5-a669-d70d363a9c34
Carry the upstream toolchain version through payload metadata and MSIX composition. Validate bundled executables without launching staged images, repair invalid caches under a cross-session installation lock, reclaim interrupted staging, and expose bundled tools only to the child PATH.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve the System.CommandLine startup and NativeAOT gates while routing setup to bundled-runtime preparation and keeping openclaw arguments transparent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(ci): finalize Artifact Signing setup

* docs: name the dedicated packaging signer

* docs: use the MSIX signing identity name

* fix(ci): recurse into signing artifact folders
* feat(ci): publish durable signed MSIX releases

* feat(ci): publish multi-architecture MSIX bundle

* fix(ci): authorize exact MSIX bundle contents

* fix(ci): use explicit zero-version signing proof
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: hannesrudolph <49103247+hannesrudolph@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
feat: bundle Node.js runtime in the Gateway MSIX
…-identity

fix: release dashboard rejects matching packaged gateway
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Attach runtime and payload matrices, clipboard checks, regression results, and authenticated Control UI screenshots.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Consume the host's semantic widget theme in the sandboxed Gateway Isolation tab while retaining system-theme fallbacks for older Control UI builds.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Chaz Gordish and others added 10 commits September 14, 2026 16:34
Apply forwarded typography, strong colors, and radii so custom Control UI themes remain visually cohesive inside the sandboxed Gateway Isolation tab.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use Windows Launcher for the user-facing plugin, tab, and page title while retaining the gateway-isolation implementation identity and reported status terminology.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove unsupported mutation guidance, validate and apply generic host theme tokens, and replace stale fallback proof with exact-head validation harnesses.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Make the isolation mode assembly-internal and update current-main test seams for the added launch argument.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pin automatic payload builds to the merged openclaw/openclaw#145409 commit and make browser proof cleanup resilient on Windows.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@ChazGo
ChazGo force-pushed the chazgo-themed-windows-launcher-plugin branch from 84511b0 to 1d44895 Compare September 14, 2026 23:36
@ChazGo ChazGo closed this Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants