Compare commits

..

513 Commits

Author SHA1 Message Date
joshp123
c8f8e92aba fix: test bundled public surface loading
Some checks failed
CI / linux (push) Has been cancelled
CI / macos (push) Has been cancelled
The stable and dogfood OpenClaw source pins need different public-surface hardlink patch shapes while upstream is still catching up. Select the right patch per source and make package contents import the OpenAI provider policy public surface, which is the path the gateway uses before a model run.

Tests: nix fmt --accept-flake-config; git diff --check; remote mac-mini nix build .#checks.aarch64-darwin.package-contents-dogfood --no-link; remote mac-mini nix build .#checks.aarch64-darwin.package-contents --no-link

Co-authored-by: Codex <noreply@openai.com>
2026-05-09 01:50:13 +08:00
joshp123
0b25e889a5 fix: keep dogfood public surface hardlink-safe
Dogfood now points at an OpenClaw commit with the broader plugin hardlink work merged, but the bundled public-surface loader still needs nix-openclaw's package-root hardlink compatibility patch. Keep that patch active for dogfood and add a package-content check that fails if the compiled loader rejects hardlinked package files again.

Tests: nix fmt --accept-flake-config; git diff --check; remote mac-mini nix build .#checks.aarch64-darwin.package-contents-dogfood --no-link; remote mac-mini nix build .#checks.aarch64-darwin.package-contents --no-link; remote mac-mini nix build .#checks.aarch64-darwin.default-instance --no-link

Co-authored-by: Codex <noreply@openai.com>
2026-05-09 01:30:55 +08:00
joshp123
26c58273e7 fix: add OpenClaw dogfood gateway build
What:\n- expose temporary dogfood package outputs pinned to an upstream OpenClaw commit with the Nix-mode fixes merged\n- let source pins disable downstream patches that are already upstream\n- build current upstream plugin assets through upstream asset hooks, while keeping the 2026.5.7 path working\n- supply the fs-safe Git dependency as an immutable Nix source for the dogfood build\n\nWhy:\n- private deployments need to dogfood upstream fixes before the next OpenClaw release without making the published stable package depend on runtime npm work\n\nTests:\n- remote Mac mini: nix build --accept-flake-config .#openclaw-gateway-dogfood --no-link\n- remote Mac mini: nix build --accept-flake-config .#openclaw-dogfood --no-link\n- remote Mac mini: nix build --accept-flake-config .#checks.aarch64-darwin.default-instance --no-link\n- remote Mac mini: nix build --accept-flake-config .#checks.aarch64-darwin.package-contents --no-link\n\nCo-authored-by: Codex <noreply@openai.com>
2026-05-09 00:37:43 +08:00
joshp123
11d69d8a1c feat: package npm runtime plugins for Nix
Add a hash-backed npm runtime plugin path that lowers OpenClaw-style npm sources into immutable plugin roots and wires them through the existing Home Manager plugin resolver. Keep flake-backed customPlugins unchanged and document the boundary for agents and maintainers.

Tests: nix build .#checks.aarch64-darwin.default-instance --no-link; nix flake check --no-build; git diff --check

Co-authored-by: Codex <noreply@openai.com>
2026-05-08 18:29:45 +08:00
joshp123
30002b7ded test: cover default OpenClaw instance on Darwin
Move the default-instance check into the common check set so Darwin evaluates the plugin/config generation path too. Keep the Linux systemd assertion and add the Darwin launchd assertion for the Home Manager module.

Tests:
- nix build --accept-flake-config .#checks.aarch64-darwin.default-instance .#checks.aarch64-darwin.package-contents .#checks.aarch64-darwin.config-validity --no-link --option narinfo-cache-negative-ttl 0
- nix build --accept-flake-config .#checks.x86_64-linux.default-instance --no-link --option narinfo-cache-negative-ttl 0
- nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link --option narinfo-cache-negative-ttl 0
- nix build --impure --accept-flake-config .#darwinConfigurations.mac-mini.system --no-link --override-input nix-openclaw path:/Users/josh/code/nix/nix-openclaw --option narinfo-cache-negative-ttl 0

Co-authored-by: Codex <noreply@openai.com>
2026-05-08 13:37:09 +08:00
joshp123
faaf1021c3 fix: separate plugin discovery from enablement
Align the openclawPlugin.plugins contract with OpenClaw itself: load paths control discovery, while plugins.entries.<id>.enabled controls activation.

Rename the contract field to enabled, reject the accidental enable spelling, and keep plugin roots on plugins.load.paths even when their generated activation default is false.

Extend the eval fixture to prove enabled=false defaults, user overrides from true to false, and user overrides from false to true.

Tests: nix build --accept-flake-config .#checks.x86_64-linux.default-instance --no-link --print-out-paths; nix eval --accept-flake-config --raw .#checks.aarch64-darwin.package-contents.drvPath; nix build --accept-flake-config .#checks.aarch64-darwin.package-contents --no-link --dry-run; nix build --impure --accept-flake-config .#darwinConfigurations.mac-mini.system --no-link --override-input nix-openclaw path:/Users/josh/code/nix/nix-openclaw --dry-run

Upstream review: fetched openclaw/openclaw origin/main at 36f847a60e and checked plugin discovery/config semantics before finalizing the contract.

Co-authored-by: Codex <noreply@openai.com>
2026-05-08 12:10:32 +08:00
joshp123
e7d60654b8 fix: package OpenClaw runtime plugin tree
Install and validate OpenClaw's dist-runtime tree so bundled runtime plugins such as ACPX are present in the Nix gateway output.

Extend the existing plugin flake contract with immutable OpenClaw plugin roots, wire those roots into generated config, and add eval fixtures proving default enablement, user overrides, and disabled entries.

Document the boundary: curated plugin artifacts are CI/Garnix-cached by nix-openclaw, while arbitrary npm or ClawHub specs need deterministic lock/hash-backed Nix artifacts cached by the user's store/cache instead of runtime npm installs.

Tests: nix build --accept-flake-config .#checks.x86_64-linux.default-instance --no-link --print-out-paths; nix eval --accept-flake-config --raw .#checks.aarch64-darwin.package-contents.drvPath; nix build --accept-flake-config .#checks.aarch64-darwin.package-contents --no-link --dry-run; nix build --impure --accept-flake-config .#darwinConfigurations.mac-mini.system --no-link --override-input nix-openclaw path:/Users/josh/code/nix/nix-openclaw --dry-run

Co-authored-by: Codex <noreply@openai.com>
2026-05-08 12:00:05 +08:00
openclaw-ci
63ff54b656 🤖 codex: mirror OpenClaw stable source v2026.5.7
What:
- update nix-openclaw to the latest stable OpenClaw source release
- refresh generated config options from that source
- keep the macOS app pin on the newest public app artifact

Why:
- keep source-built OpenClaw current without blocking on public macOS app asset lag

Tests:
- nix build .#checks.x86_64-linux.ci --accept-flake-config
- nix build .#checks.aarch64-darwin.ci --accept-flake-config
- scripts/hm-activation-macos.sh
2026-05-07 23:41:24 +00:00
openclaw-ci
a9494d1b8b 🤖 codex: mirror OpenClaw stable source v2026.5.7
Some checks are pending
CI / linux (push) Waiting to run
CI / macos (push) Waiting to run
What:
- update nix-openclaw to the latest stable OpenClaw source release
- refresh generated config options from that source
- keep the macOS app pin on the newest public app artifact

Why:
- keep source-built OpenClaw current without blocking on public macOS app asset lag

Tests:
- nix build .#checks.x86_64-linux.ci --accept-flake-config
- nix build .#checks.aarch64-darwin.ci --accept-flake-config
- scripts/hm-activation-macos.sh
2026-05-07 21:46:51 +00:00
joshp123
44ae6995de Trigger CI after yolo promotion
What:
- let the yolo promote job dispatch the CI workflow after pushing main
- grant the promote job actions:write for workflow_dispatch

Why:
- GitHub suppresses push-triggered workflows created by GITHUB_TOKEN, so yolo-promoted commits otherwise lack a CI run on the final main SHA

Tests:
- scripts/check-flake-lock-owners.sh
- node scripts/select-openclaw-release.test.mjs
- bash -n scripts/update-pins.sh
- ruby -e 'require "yaml"; ARGV.each { |f| YAML.load_file(f) }' .github/workflows/*.yml
- GITHUB_ACTIONS=true scripts/update-pins.sh select
- nix flake show --accept-flake-config

Co-authored-by: Codex <noreply@openai.com>
2026-05-07 11:04:00 +02:00
openclaw-ci
a2ea92cce2 🤖 codex: mirror OpenClaw stable source v2026.5.6
Some checks are pending
CI / macos (push) Waiting to run
CI / linux (push) Waiting to run
What:
- update nix-openclaw to the latest stable OpenClaw source release
- refresh generated config options from that source
- keep the macOS app pin on the newest public app artifact

Why:
- keep source-built OpenClaw current without blocking on public macOS app asset lag

Tests:
- nix build .#checks.x86_64-linux.ci --accept-flake-config
- nix build .#checks.aarch64-darwin.ci --accept-flake-config
- scripts/hm-activation-macos.sh
2026-05-06 19:06:42 +00:00
joshp123
4ed579792e docs: record OpenClaw plugin investigations
What:
- document the QMD/mcporter packaging decision for maintainers
- capture the native OpenClaw plugin gap and proposed nix-openclaw fix

Why:
- preserve the Discord investigation so the feature work can resume later
- distinguish PR #81's bundled manifest fix from external native plugin support

Tests:
- git diff --check: pass
- nix build .#checks.aarch64-darwin.package-contents --no-link: pass

Co-authored-by: Codex <noreply@openai.com>
2026-05-06 16:37:48 +02:00
joshp123
73e9320cac Replace read-only workspace directories
Chmod existing materialized OpenClaw workspace targets before removal so Nix can update copied skill directories from previous generations.

Tests: manual materializer smoke replacing read-only directory; nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link
2026-05-06 16:09:52 +02:00
joshp123
794cf476d9 Make materialized workspace entries declarative
Configured OpenClaw documents and skills are Nix-owned targets. Replace them during activation instead of blocking on stale copied files from older generations.

Tests: manual materializer smoke replacing an existing writable target; nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link
2026-05-06 16:03:25 +02:00
joshp123
2d0a67d190 Adopt read-only managed workspace files
Allow the workspace materializer to replace read-only existing targets when the state manifest is missing, while still refusing writable user-owned files. This keeps Nix-owned OpenClaw docs deployable across module upgrades.

Tests: manual materializer smoke for read-only adopt and writable refusal; nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link
2026-05-06 16:00:36 +02:00
joshp123
ab4bbf5dba Force Nix-owned OpenClaw config links
Home Manager should replace the generated openclaw.json target because Nix owns that file. Without force=true, Darwin deploys can fail when the existing config symlink points at the previous generation.

Tests: nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link
2026-05-06 15:57:12 +02:00
joshp123
cbe10a8eb9 Report installed OpenClaw command names
Render TOOLS.md from actual package outputs instead of requested tool attrs, and include per-instance runtime packages so agent docs match the commands on PATH.

Tests: nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link
2026-05-06 15:54:29 +02:00
joshp123
68b04bd0b3 Allow direct OpenClaw package module use
Do not require the nix-openclaw overlay when a downstream Home Manager config explicitly supplies the OpenClaw package. This keeps consumer hosts thin and lets them use flake package outputs directly.

Tests: nix eval --accept-flake-config .#checks.x86_64-linux.default-instance.drvPath; nix build --accept-flake-config .#checks.aarch64-darwin.config-validity --no-link
2026-05-06 14:52:16 +02:00
joshp123
694349643f Update bundled plugin tools pin
Point Home Manager bundled plugin sources at the current nix-openclaw-tools commit so Darwin QMD plugin installs use the fixed packaged tool.

Tests: nix eval --accept-flake-config .#checks.x86_64-linux.default-instance.drvPath; nix build --accept-flake-config .#checks.aarch64-darwin.config-validity --no-link
2026-05-06 14:47:32 +02:00
joshp123
54e09bce18 Expose runtime tools to OpenClaw Codex harness
Add Home Manager runtimePackages/environment options that feed the gateway wrapper without polluting the user PATH. Link the same runtime package set into Codex's isolated agent home so shell calls from the Codex harness see Nix-managed plugin and helper CLIs.

Tests: ./scripts/check-flake-lock-owners.sh; nix flake show --accept-flake-config; nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link; nix build --accept-flake-config .#checks.aarch64-darwin.qmd-runtime .#checks.aarch64-darwin.bin-surface .#checks.aarch64-darwin.package-contents --no-link; nix eval --accept-flake-config .#checks.x86_64-linux.default-instance.drvPath; ./scripts/hm-activation-macos.sh
2026-05-06 14:44:56 +02:00
joshp123
fd30aad492 Publish OpenClaw packages from Garnix
Green CI alone is not enough for downstream Nix consumers: the user-facing and gateway package outputs must be top-level Garnix artifacts so machines can substitute them without local OpenClaw builds.

Tests: git diff --check
2026-05-06 13:24:56 +02:00
joshp123
7cbc79ce52 Install only bundled plugin manifests
The gateway runtime loads built plugins from dist/extensions, so the source extensions tree only needs manifest metadata for compatibility. Copy only openclaw.plugin.json files and assert the memory-core manifest exists in both compatibility and runtime trees.

Tests: sh -n nix/scripts/gateway-install.sh nix/scripts/check-package-contents.sh; git diff --check; nix build --dry-run .#checks.aarch64-darwin.package-contents .#checks.x86_64-linux.package-contents --accept-flake-config
2026-05-06 13:08:57 +02:00
joshp123
0dae522382 Build gateway outside the output tree
Use a normal temporary build root and copy only final runtime outputs into the Nix output. This avoids the Garnix stall caused by recursively deleting the temporary OpenClaw source tree from inside $out during install.

Tests: sh -n nix/scripts/build-root.sh nix/scripts/gateway-install.sh; git diff --check; nix build --dry-run .#checks.aarch64-darwin.qmd-runtime .#checks.x86_64-linux.qmd-runtime --accept-flake-config
2026-05-06 12:59:49 +02:00
joshp123
87f883a6c8 Speed up gateway build-root cleanup
Move the temporary output build root out of the final output instead of recursively deleting it during install, and time the final wrapper/cleanup steps so Garnix logs show where gateway builds stall.

Tests: sh -n nix/scripts/build-root.sh nix/scripts/gateway-install.sh; git diff --check; nix build --dry-run .#checks.aarch64-darwin.qmd-runtime .#checks.x86_64-linux.qmd-runtime --accept-flake-config
2026-05-06 12:51:50 +02:00
joshp123
cb9ec56612 Bound pnpm store extraction threads
Use NIX_BUILD_CORES for zstd extraction instead of --threads=0. Garnix macOS builders were hanging in the pnpm-store extraction step on the fresh OpenClaw gateway build.

Tests: git diff --check. Full package proof is delegated to Garnix because the fresh v2026.5.5 gateway source build is intentionally not run locally.
2026-05-06 12:26:35 +02:00
joshp123
6a5352eba7 Avoid duplicate gateway shebang patch
Remove the install-phase patchShebangs pass over node_modules/.bin. The build phase already runs patchShebangs before packaging, and the second pass can hang Garnix while not materially changing the output.

Tests: git diff --check. Full package proof is delegated to Garnix because the fresh v2026.5.5 gateway source build is intentionally not run locally.
2026-05-06 12:07:58 +02:00
joshp123
e4aad2be37 Use fixed OpenClaw tools QMD package
Update nix-openclaw-tools to the Darwin QMD package that provides xcbuild for node-gyp in Garnix.

Tests: nix build .#checks.aarch64-darwin.qmd-runtime --no-link --accept-flake-config before rebasing onto the v2026.5.5 source bump; nix build .#checks.x86_64-linux.qmd-runtime --no-link --accept-flake-config before rebasing onto the v2026.5.5 source bump; git diff --check. The v2026.5.5 Darwin gateway build is left to CI/Garnix to avoid a large local source build.
2026-05-06 11:45:02 +02:00
openclaw-ci
b46482a4df 🤖 codex: mirror OpenClaw stable source v2026.5.5
What:
- update nix-openclaw to the latest stable OpenClaw source release
- refresh generated config options from that source
- keep the macOS app pin on the newest public app artifact

Why:
- keep source-built OpenClaw current without blocking on public macOS app asset lag

Tests:
- nix build .#checks.x86_64-linux.ci --accept-flake-config
- nix build .#checks.aarch64-darwin.ci --accept-flake-config
- scripts/hm-activation-macos.sh
2026-05-06 09:27:39 +00:00
joshp123
3a0c35bf68 Fix QMD model prewarm activation
Use QMD's real update/embed/query commands instead of the nonexistent qmd pull path, and keep the activation script in nix/scripts instead of inline Nix.

Tests: nix build .#checks.aarch64-darwin.config-validity .#checks.aarch64-darwin.qmd-runtime --no-link; nix eval .#checks.x86_64-linux.qmd-runtime.name; scripts/check-flake-lock-owners.sh; git diff --check
2026-05-06 11:09:33 +02:00
joshp123
7f55c0bd7b Pin safe-write Python in OpenClaw wrapper
Bundle a Nix Python path inside the OpenClaw batteries wrapper for the safe-write helper. This avoids macOS /usr/bin/python3 triggering the Xcode command-line-tools shim on headless hosts, without exposing Python on the user PATH.

Tests: nix build .#checks.aarch64-darwin.qmd-runtime --no-link; nix eval .#checks.x86_64-linux.qmd-runtime.name; scripts/check-flake-lock-owners.sh; git diff --check
2026-05-06 10:37:25 +02:00
joshp123
32f0772a59 Use upstream QMD on Linux
Keep the QMD package internal to OpenClaw, but source it from upstream tobi/qmd on Linux and from the repaired nix-openclaw-tools package on Darwin until upstream Darwin packaging works.

Tests: scripts/check-flake-lock-owners.sh; nix build .#checks.aarch64-darwin.qmd-runtime --no-link; nix eval .#packages.x86_64-linux.qmd.outPath; nix eval .#packages.aarch64-darwin.qmd.outPath; git diff --check
2026-05-06 10:31:54 +02:00
joshp123
505c50fa58 Use upstream QMD on Linux
Keep upstream tobi/qmd as the Linux QMD package while retaining the nix-openclaw-tools Darwin repair package until upstream Darwin is fixed. This preserves the batteries-included runtime path without forking the Linux package surface.

Tests: nix build '.#checks.aarch64-darwin.qmd-runtime' --accept-flake-config --no-link; nix eval --option eval-cache false --raw '.#checks.x86_64-linux.qmd-runtime.name' --accept-flake-config; nix eval --option eval-cache false --raw '.#checks.aarch64-darwin.qmd-runtime.name' --accept-flake-config; git diff --check
2026-05-06 10:01:46 +02:00
joshp123
d56fa8a75c consume QMD through OpenClaw tools
What:
- consume QMD from nix-openclaw-tools instead of a separate upstream flake input
- expose QMD as an internal OpenClaw battery on Darwin and Linux
- add an opt-in Home Manager qmd model prewarm activation
- keep plugin packages off the user's shell PATH by default while preserving the runtime PATH

Why:
- nix-openclaw-tools owns reproducible tool packages and cacheable plugin metadata
- nixos-config should configure OpenClaw, not hand-wire runtime tools

Tests:
- nix build .#checks.aarch64-darwin.package-contents --accept-flake-config --no-link
- nix build .#checks.aarch64-darwin.qmd-runtime --accept-flake-config --no-link
- nix build .#checks.aarch64-darwin.bin-surface .#checks.aarch64-darwin.config-validity .#checks.aarch64-darwin.gateway-smoke --accept-flake-config --no-link
- nix eval .#checks.x86_64-linux.default-instance.drvPath --accept-flake-config
2026-05-06 09:44:37 +02:00
joshp123
0a70262dda run OpenClaw runtime postbuild in Nix
What:
- run upstream runtime-postbuild after tsdown in the gateway build
- assert runtime-model-auth stable alias points at a real generated target

Why:
- bundled provider auth imports need the upstream stable runtime alias materialized in the Nix package
- package-contents should catch missing runtime aliases before deployment

Tests:
- nix build .#checks.aarch64-darwin.package-contents --accept-flake-config --no-link
- nix build .#checks.aarch64-darwin.qmd-runtime --accept-flake-config --no-link
- nix build .#checks.aarch64-darwin.bin-surface .#checks.aarch64-darwin.config-validity .#checks.aarch64-darwin.gateway-smoke --accept-flake-config --no-link
2026-05-06 09:44:36 +02:00
joshp123
5e186c192f docs: split maintainer agent guidance
Move public maintainer policy and automation guidance into maintainers/, shrink root AGENTS.md into an audience router, and ignore internal .agent scratch state.

Delete tracked internal ExecPlans from .agent and scrub personal/private references from public RFC examples.

Checks: git diff --cached --check; scripts/check-flake-lock-owners.sh; node scripts/select-openclaw-release.test.mjs; bash -n scripts/update-pins.sh; ruby -e 'require "yaml"; YAML.load_file(".github/workflows/yolo-update.yml"); YAML.load_file(".github/workflows/ci.yml")'
2026-05-06 09:29:46 +02:00
joshp123
d213d242e7 docs: define OpenClaw Nix boundaries 2026-05-06 08:53:40 +02:00
joshp123
879885aecd docs: separate public and maintainer agent guidance
Clarify that AGENTS.md is maintainer operating guidance for the public packaging repo, not consumer onboarding or private deployment policy.

Remove the private bot reference, generalize recovery wording, and keep automation focused on the public split-track publishing invariant.

Checks: git diff --check -- AGENTS.md; scripts/check-flake-lock-owners.sh
2026-05-06 08:14:30 +02:00
joshp123
51c69b6181 docs: define maintainer automation end state
Describe the daily nix-openclaw maintainer run by desired publishing state instead of enumerated failure modes.

The automation must restore split-track freshness for latest source gateway and latest published macOS app, or report the exact blocker.

Checks: git diff --check -- AGENTS.md; scripts/check-flake-lock-owners.sh
2026-05-06 07:53:25 +02:00
joshp123
0995d25074 docs: clarify nix-openclaw freshness policy
Define daily maintainer automation health as split-track freshness: gateway tracks latest stable source, while the Darwin app tracks the newest published public macOS zip.

This prevents expected source/app version differences from being treated as failures, while requiring pipeline repair when either track is stale.

Checks: git diff --check -- AGENTS.md; scripts/check-flake-lock-owners.sh
2026-05-06 07:39:24 +02:00
joshp123
ce0da31f81 fix: skip QMD bundle on Darwin
QMD currently pulls a native better-sqlite3 rebuild that fails on Garnix Darwin because node-gyp cannot find Xcode/CLT in the builder.

Keep QMD bundled and checked on Linux, but omit it from Darwin packages until the QMD package is Darwin-cacheable.

Tests: nix eval --accept-flake-config --json .#checks.aarch64-darwin --apply 'builtins.attrNames'; nix eval --accept-flake-config --json .#checks.x86_64-linux --apply 'builtins.attrNames'; nix build --accept-flake-config .#checks.aarch64-darwin.ci --no-link --print-build-logs
2026-05-05 23:11:58 +02:00
joshp123
e93384082a fix: keep Nix OpenClaw config immutable
Some checks failed
CI / linux (push) Has been cancelled
CI / macos (push) Has been cancelled
What:
- make the downstream Nix-mode auto-enable patch runtime-only and remove broken degraded-state references
- allow plugin-owned channels.<id> config in generated Home Manager options
- add Telegram channel config coverage to the config validity check
- document the Nix/OpenClaw boundary in AGENTS.md

Why:
- Nix-owned openclaw.json must not be mutated at runtime
- plugin channel config should stay valid even when upstream core schema does not type every plugin-owned channel key
- future agents need the boundary documented in the packaging repo

Tests:
- patch -d /tmp/openclaw-v2026.5.4 -p1 --dry-run < nix/patches/skip-plugin-auto-enable-persist-in-nix-mode.patch: passed
- generator round-trip against OpenClaw 325df3ef produced no diff: passed
- nix eval --accept-flake-config --raw .#checks.aarch64-darwin.config-validity.drvPath: passed
- nix eval --accept-flake-config --raw .#checks.x86_64-linux.config-options.drvPath: passed
- nix build --accept-flake-config .#checks.aarch64-darwin.config-validity --no-link --print-build-logs: passed
2026-05-05 22:26:40 +02:00
joshp123
3abd2d14cb feat: bundle QMD for opt-in local memory
Make QMD the Nix-supported batteries-included local memory backend by pinning the upstream QMD flake and adding qmd to the private openclaw wrapper PATH.

Keep QMD opt-in through upstream OpenClaw config with memory.backend = qmd, and document that builtin memorySearch.provider = local remains an escape hatch rather than the primary supported Nix path.

Also point nix run .#openclaw at the batteries-included bundle so app execution gets the same internal runtime PATH as the package.

Tests: sh -n nix/scripts/check-openclaw-qmd-runtime.sh; scripts/check-flake-lock-owners.sh; git diff --check; nix flake show --accept-flake-config --json; nix build .#checks.aarch64-darwin.qmd-runtime .#checks.aarch64-darwin.bin-surface .#packages.aarch64-darwin.openclaw --accept-flake-config --no-link --print-out-paths; nix build .#checks.x86_64-linux.qmd-runtime --accept-flake-config --no-link --print-out-paths; nix build .#checks.aarch64-darwin.ci --accept-flake-config --no-link --print-out-paths; nix build .#packages.x86_64-linux.openclaw .#checks.x86_64-linux.bin-surface --accept-flake-config --no-link --print-out-paths; scripts/hm-activation-macos.sh; nix build .#checks.x86_64-linux.gateway-smoke --accept-flake-config --no-link --print-out-paths; nix run .#openclaw --accept-flake-config -- --version; bash -n scripts/update-pins.sh; node --check scripts/select-openclaw-release.mjs; node --check scripts/select-openclaw-release.test.mjs; node scripts/select-openclaw-release.test.mjs
2026-05-05 20:24:25 +02:00
joshp123
4a918c46ee fix: allow package public surface hardlinks
OpenClaw v2026.5.4 resolves bundled plugin public artifacts under the package-root extension surface. In the Nix store those artifacts may be hardlinked, so allow hardlinks for resolved module paths inside OPENCLAW_PACKAGE_ROOT while keeping the existing hardlink rejection elsewhere.

Tests: nix build .#checks.aarch64-darwin.gateway-smoke --accept-flake-config --no-link --print-out-paths; nix build .#checks.aarch64-darwin.ci --accept-flake-config --no-link --print-out-paths; nix build .#packages.x86_64-linux.openclaw .#checks.x86_64-linux.bin-surface .#checks.x86_64-linux.gateway-smoke --accept-flake-config --no-link --print-out-paths; scripts/check-flake-lock-owners.sh; scripts/hm-activation-macos.sh
2026-05-05 19:22:50 +02:00
openclaw-ci
8264853833 🤖 codex: mirror OpenClaw stable source v2026.5.4
What:
- update nix-openclaw to the latest stable OpenClaw source release
- refresh generated config options from that source
- keep the macOS app pin on the newest public app artifact

Why:
- keep source-built OpenClaw current without blocking on public macOS app asset lag

Tests:
- nix build .#checks.x86_64-linux.ci --accept-flake-config
- nix build .#checks.aarch64-darwin.ci --accept-flake-config
- scripts/hm-activation-macos.sh
2026-05-05 17:01:48 +00:00
joshp123
8c6267b2c3 fix: decouple source and mac app release pins
Select the latest stable OpenClaw source release independently from the newest public macOS app artifact. Report app lag instead of skipping source releases without desktop assets, and teach yolo to materialize source/app pins separately.

Tests: bash -n scripts/update-pins.sh; node --check scripts/select-openclaw-release.mjs scripts/select-openclaw-release.test.mjs; node scripts/select-openclaw-release.test.mjs; GITHUB_ACTIONS=true scripts/update-pins.sh select; ruby -e 'require "yaml"; YAML.load_file(".github/workflows/yolo-update.yml")'; nix flake show --accept-flake-config --json; git diff --check
2026-05-05 18:22:33 +02:00
joshp123
eb9a52bf96 fix: keep runtime tools internal
Expose only the openclaw command from the default package while keeping bundled runtime tools on the wrapper PATH. Remove the public openclaw-tools package output and document runtime tools as implementation detail.

Tests: nix build .#packages.aarch64-darwin.openclaw .#checks.aarch64-darwin.bin-surface .#packages.x86_64-linux.openclaw .#checks.x86_64-linux.bin-surface --accept-flake-config --no-link --print-out-paths; nix build .#checks.aarch64-darwin.ci --accept-flake-config --no-link --print-out-paths; git diff --check --cached
2026-05-05 18:14:08 +02:00
joshp123
33f8f87571 docs: codify trunk maintainer workflow
Make the repo-local agent rules explicit: work on main by default, push surgical commits directly, and verify CI per pushed commit.

This prevents completed nix-openclaw maintainer work from being parked on Codex branches.

Tests: git diff --check
2026-05-05 15:48:56 +02:00
joshp123
c6a4cfc9d7 fix: keep plugin auto-enable declarative in Nix mode
Patch the gateway startup path so OPENCLAW_NIX_MODE skips plugin auto-enable persistence instead of replacing a Nix-managed config symlink.

Plugins remain a declarative Nix config choice under the Home Manager module; runtime auto-enable can still report what it would have changed.

Tests: git diff --cached --check; patch -p1 --dry-run against pinned OpenClaw source; nix build .#packages.aarch64-darwin.openclaw-gateway --accept-flake-config --no-link --print-out-paths; OPENCLAW_NIX_MODE symlink-clobber smoke against the native gateway; nix build .#packages.x86_64-linux.openclaw-gateway --accept-flake-config --no-link --print-out-paths
2026-05-05 15:48:56 +02:00
joshp123
2149201a95 fix: keep workspace materialization sources in closure
Generate a Nix-owned source/target manifest for workspace documents and skills, then pass that manifest to the activation helper.

This keeps document and plugin source paths in the Home Manager generation closure so the NixOS VM can actually copy them during activation.

Tests: helper smoke; git diff --check; nix build .#checks.x86_64-linux.default-instance --accept-flake-config --no-link --print-out-paths; nix build .#checks.x86_64-linux.hm-activation.nodes.machine.system.build.toplevel --accept-flake-config --no-link --print-out-paths
2026-05-05 15:45:11 +02:00
joshp123
7471da32e5 fix: materialize workspace docs and skills
What:
- copy Nix-managed documents and skills into the OpenClaw workspace as real files
- replace Home Manager symlink installs with an activation-time materialization helper
- extend checks to assert custom plugin skills and document files are not symlinks

Why:
- OpenClaw rejects workspace files that resolve back into the Nix store
- custom plugin skills and documents must satisfy the gateway workspace boundary

Tests:
- git diff --cached --check: passed
- nix/modules/home-manager/openclaw-materialize-workspace-files.sh smoke: copied docs and skill dirs as non-symlinks, rerun idempotent
- temporary worktree with only this staged patch: nix build #checks.x86_64-linux.default-instance --accept-flake-config --no-link --print-out-paths: /nix/store/2zihci7mhlk3mcbczmyw0s401n162vk7-openclaw-default-instance-1
- temporary worktree with only this staged patch: nix build #checks.x86_64-linux.hm-activation --accept-flake-config --no-link --print-out-paths: materialization assertions passed; later gateway open-port wait timed out under local TCG VM after 900s
2026-05-05 15:00:07 +02:00
the sun gif man
bf7764385a
Revise contributions section in README
Updated contribution guidelines to emphasize Discord communication and clarify PR process.
2026-05-05 14:51:45 +02:00
joshp123
e739f5888f fix: install bundled gateway skills
What:
- copy upstream bundled skills into the gateway package output
- make package-contents require bundled SKILL.md files under lib/openclaw/skills

Why:
- upstream bundled skills are part of the runnable OpenClaw package contract
- package checks should catch missing skills before users hit runtime failures

Tests:
- git diff --cached --check: passed
- temporary worktree with only this staged patch: nix build #checks.x86_64-linux.package-contents --accept-flake-config --no-link --print-out-paths: /nix/store/zqwb4x85cwww8fx5gzj0asxy1ic5i373-openclaw-package-contents-unstable-8b2a6e57
2026-05-05 13:18:39 +02:00
joshp123
8b24b5d515 fix: generate usable secret provider options
What:
- flatten source-discriminated object unions when the variants have different fields
- regenerate secrets.providers as one submodule with a source enum and nullable variant fields
- add a default-instance regression for file-backed secret providers

Why:
- Nix cannot reliably merge attrsOf oneOf submodules for secrets.providers
- the generated module should preserve upstream config shape while staying evaluable

Tests:
- git diff --cached --check: passed
- temporary worktree with only this staged patch: nix build #checks.x86_64-linux.default-instance --accept-flake-config --no-link --print-out-paths: /nix/store/2zihci7mhlk3mcbczmyw0s401n162vk7-openclaw-default-instance-1
- temporary worktree with only this staged patch: nix build #checks.x86_64-linux.config-options --accept-flake-config --no-link --print-out-paths: /nix/store/4yrjhllg88ydyf70yqnkdmndbrv7y2c6-openclaw-source-checks-unstable-8b2a6e57
2026-05-05 13:07:48 +02:00
joshp123
216f835d24 fix: preserve default gateway mode
What:
- strip generated null defaults before merging user and instance OpenClaw config
- assert the default generated config keeps gateway.mode = "local"

Why:
- generated submodule defaults should not silently erase package-owned base config
- the default Home Manager instance must remain runnable without extra gateway config

Tests:
- git diff --cached --check: passed
- temporary worktree with only this staged patch: nix build #checks.x86_64-linux.default-instance --accept-flake-config --no-link --print-out-paths: /nix/store/2zihci7mhlk3mcbczmyw0s401n162vk7-openclaw-default-instance-1
2026-05-05 12:33:06 +02:00
joshp123
3333bb831e docs: repair agent-first onboarding
What:
- export the agent-first flake template
- remove unsupported Intel macOS setup claims
- replace stale Discord channel links with the public invite path
- simplify the advanced dual-instance example and sync plugin docs

Why:
- make the documented first-run path match the flake users actually consume
- avoid sending users toward unsupported systems or dead Discord links

Tests:
- git diff --cached --check: passed
- nix flake show --json --accept-flake-config | jq -e '.templates."agent-first"': passed
- nix flake init -t /Users/josh/code/nix-openclaw#agent-first --accept-flake-config: wrote flake.nix and documents/
- rg stale onboarding strings in README.md AGENTS.md templates/agent-first/flake.nix docs: no matches
2026-05-05 12:30:36 +02:00
joshp123
e16f9743fd chore: rename first-party tools flake input
What:
- replace nix-steipete-tools with nix-openclaw-tools across flake wiring
- pass first-party tool packages through the overlay and package set explicitly
- update the bundled plugin catalog for the renamed tool repo

Why:
- keep nix-openclaw aligned with the upstream OpenClaw tool repo rename
- avoid stale steipete naming in package and plugin resolution

Tests:
- git diff --cached --check: passed
- scripts/check-flake-lock-owners.sh: passed
- nix eval --raw .#packages.x86_64-linux.openclaw-tools.name --accept-flake-config: openclaw-tools
- nix eval --json --impure --expr '<overlay toolNames eval>': returned node/pnpm/core plus gogcli, goplaces, summarize, camsnap, sonoscli
2026-05-05 12:29:59 +02:00
joshp123
ecfb1dc936 ci: update GitHub Actions runtimes
What:
- bump actions/checkout from v4 to v6 in CI and yolo workflows
- bump DeterminateSystems/nix-installer-action from v13 to v22

Why:
- remove Node 20 action runtime warnings before GitHub's Node 24 migration window
- keep the packaging pipelines on maintained action runtimes

Tests:
- ruby -e 'require "yaml"; YAML.load_file(".github/workflows/ci.yml"); YAML.load_file(".github/workflows/yolo-update.yml")'
- git diff --check
2026-05-05 10:59:39 +02:00
joshp123
ba7d1573a4 ci: focus OpenClaw checks on Nix package contract
What:
- replace the misleading gateway test check with a source-checks build/config-options check
- remove the full upstream Vitest suite from the hard Nix promotion gate
- document that yolo validates the Nix-owned package contract, not upstream source test health

Why:
- the current full packageable upstream release builds as a package but has failing upstream Vitest cases when rebuilt from source
- nix-openclaw should block on packaging, smoke startup, config generation, module activation, and app artifacts rather than an upstream-owned unit test suite

Tests:
- git diff --check
- bash -n scripts/update-pins.sh scripts/hm-activation-macos.sh nix/modules/home-manager/openclaw-launchd-relink.sh nix/scripts/source-checks-build.sh nix/scripts/source-checks-check.sh nix/scripts/config-options-check.sh
- ruby -e 'require "yaml"; YAML.load_file(".github/workflows/ci.yml"); YAML.load_file(".github/workflows/yolo-update.yml")'
- nix eval --accept-flake-config --raw .#checks.x86_64-linux.source-checks.drvPath
- nix eval --accept-flake-config --raw .#checks.x86_64-linux.ci.drvPath
- nix flake show --accept-flake-config --all-systems --json
- scripts/check-flake-lock-owners.sh
- node scripts/select-openclaw-release.test.mjs
- scripts/hm-activation-macos.sh
- nix build --accept-flake-config -L .#checks.aarch64-darwin.ci
2026-05-05 10:52:55 +02:00
joshp123
94fa2e2ef3 ci: use standard Linux runners
Blacksmith Linux hung twice during final verification while the same check passed once in between. Move Linux validation back to ubuntu-latest and keep explicit timeouts so the packaging gate favors reliability over runner speed.

Verification: git diff --check; ruby YAML parse
2026-05-05 10:34:18 +02:00
joshp123
9c2b207501 ci: bound yolo release validation
Apply the same timeout discipline to the automated OpenClaw pin updater so release validation cannot hang indefinitely before rewriting main.

Verification: git diff --check; ruby YAML parse
2026-05-05 10:17:17 +02:00
joshp123
c8782f7631 ci: bound OpenClaw packaging checks
Add explicit CI timeouts around the Linux and macOS packaging gates so a wedged OpenClaw build fails inspectably instead of hanging indefinitely.

Verification: git diff --check; ruby YAML parse
2026-05-05 10:07:08 +02:00
joshp123
d9b42b0f77 test: cover OpenClaw plugin surface
Fix the documented minimal Home Manager plugin option to use customPlugins, remove the stale duplicate skill-file builder, and assert duplicate plugin skill paths against the paths Home Manager actually installs.

Add local plugin fixtures so the default-instance check covers the agent-facing customPlugins path and duplicate skill collisions.
2026-05-05 09:29:34 +02:00
joshp123
123f0a7cc4 docs: harden maintainer automation context
What:
- move the completed OpenClaw packaging ExecPlan out of the pending slot
- document recursive self-review and the macOS Home Manager activation gate for daily maintenance
- make macOS app publishing explicitly out of scope for nix-openclaw automation
- replace an unclear README ownership label with release automation

Why:
- future maintainer runs need enough durable context to repair nix-openclaw without relearning this thread
- missing upstream macOS assets should be classified, not turned into a competing release process

Tests:
- git diff --check
- node scripts/select-openclaw-release.test.mjs
- bash -n scripts/update-pins.sh
- ruby -e 'require "yaml"; YAML.load_file(".github/workflows/yolo-update.yml"); YAML.load_file(".github/workflows/ci.yml")'
- scripts/check-flake-lock-owners.sh
- nix flake show --accept-flake-config
2026-05-05 08:37:36 +02:00
joshp123
a468354c55 fix: harden OpenClaw Nix release packaging
What:
- make openclaw the canonical agent-first package surface and document the maintainer automation contract
- select the newest full packageable OpenClaw stable release and report newer assetless stable releases
- validate public CLI config, smoke-test gateway runtime, and share source checks across gateway tests/config option generation
- fix Nix-store hardlink runtime handling and Linux builder scratch space
- add daily Codex maintainer automation for direct-to-main repair after full gates
- block yolo promotion if promote materializes a different diff than validation

Why:
- keep nix-openclaw reliable for macOS and Linux users while upstream release assets lag source tags
- make yolo and daily automation uphold one clean Nix package contract

Tests:
- node scripts/select-openclaw-release.test.mjs
- bash -n scripts/update-pins.sh
- sh -n nix/scripts/build-root.sh nix/scripts/gateway-install.sh nix/scripts/gateway-prebuild.sh nix/scripts/gateway-tests-check.sh nix/scripts/source-checks-check.sh
- ruby -e 'require "yaml"; YAML.load_file(".github/workflows/yolo-update.yml")'
- scripts/check-flake-lock-owners.sh
- GITHUB_ACTIONS=true scripts/update-pins.sh select
- nix flake show --accept-flake-config
- nix build .#checks.aarch64-darwin.ci --accept-flake-config --max-jobs 1 -L
- nix build .#checks.x86_64-linux.ci --accept-flake-config --max-jobs 1 -L
2026-05-05 08:00:13 +02:00
joshp123
53aac0dce0 fix(gateway-tests): follow upstream vitest config path
Some checks failed
CI / linux (push) Has been cancelled
CI / macos (push) Has been cancelled
OpenClaw v2026.4.14 moved the gateway Vitest config under test/vitest/, but
our Nix gateway test seam still hard-coded the old root-level path. Linux CI
therefore failed before tests even started.

Prefer the old path when present, but fall back to the new upstream path so the
stable release mirror works across both layouts.

Tests:
- bash -n nix/scripts/gateway-tests-check.sh
2026-04-14 18:15:58 +02:00
joshp123
ed86924dd3 fix(release): hash the app zip the way fetchzip expects
The stable mirror had advanced main to v2026.4.14 with an app hash that only
matched prefetch output, not the unpacked tree hash that fetchzip validates.
That left macOS CI red on the pinned stable release.

Compute the app hash from the unpacked zip contents in update-pins.sh and fix
the current v2026.4.14 app pin to the actual fetchzip hash.

Tests:
- bash -n scripts/update-pins.sh
- nix build .#openclaw-app --accept-flake-config -L
2026-04-14 18:15:54 +02:00
joshp123
34e5f011b9 fix(ci): validate stable release bumps before promotion
What:
- split the stable-release updater into read-only selection and pin materialization modes
- rewrite yolo into select, validate-linux, validate-macos, and promote jobs
- fail yolo when the newest stable release is incomplete instead of silently sticking
- update maintainer docs to describe the new safe promotion policy

Why:
- stop direct yolo pushes from moving main without the same Linux and macOS proof as CI
- keep mirroring the newest stable release while making broken upstream releases visible

Tests:
- bash -n scripts/update-pins.sh
- ruby -e 'require "yaml"; YAML.load_file(".github/workflows/yolo-update.yml"); puts "yaml-ok"'
- GITHUB_ACTIONS=true GH_TOKEN="$(gh auth token)" scripts/update-pins.sh select
- temp copy pinned to v2026.4.11: scripts/update-pins.sh select emits v2026.4.14 tuple
2026-04-14 18:10:25 +02:00
openclaw-ci
b023ed119f 🤖 codex: mirror OpenClaw stable release v2026.4.14
What:
- update nix-openclaw to the latest stable OpenClaw release
- refresh the gateway source pin, app asset pin, and generated config options

Why:
- keep nix-openclaw aligned with upstream stable releases instead of upstream main churn

Tests:
- nix build .#openclaw-gateway --accept-flake-config
2026-04-14 15:20:34 +00:00
openclaw-ci
13deaaf73f 🤖 codex: mirror OpenClaw stable release v2026.4.11
Some checks failed
CI / linux (push) Has been cancelled
CI / macos (push) Has been cancelled
What:
- update nix-openclaw to the latest stable OpenClaw release
- refresh the gateway source pin, app asset pin, and generated config options

Why:
- keep nix-openclaw aligned with upstream stable releases instead of upstream main churn

Tests:
- nix build .#openclaw-gateway --accept-flake-config
2026-04-12 01:05:06 +00:00
openclaw-ci
a003810ddd 🤖 codex: mirror OpenClaw stable release v2026.4.10
Some checks are pending
CI / linux (push) Waiting to run
CI / macos (push) Waiting to run
What:
- update nix-openclaw to the latest stable OpenClaw release
- refresh the gateway source pin, app asset pin, and generated config options

Why:
- keep nix-openclaw aligned with upstream stable releases instead of upstream main churn

Tests:
- nix build .#openclaw-gateway --accept-flake-config
2026-04-11 04:50:58 +00:00
joshp123
c2e8301f51 fix(ci): resolve vitest entrypoint in gateway tests
Some checks are pending
CI / linux (push) Waiting to run
CI / macos (push) Waiting to run
What:
- restore the direct vitest entrypoint lookup in nix/scripts/gateway-tests-check.sh
- execute the discovered vitest module with node instead of relying on pnpm exec lookup

Why:
- the history rewrite dropped one real recovery fix and reintroduced the gateway-tests runner failure on Linux
- the green line already proved the direct-entrypoint approach works reliably in the Nix check environment

Tests:
- verified failing CI run 24246896697: ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL Command "vitest" not found
2026-04-10 16:08:44 +02:00
joshp123
4e93f4ac56 feat(releases): mirror OpenClaw stable releases
What:
- switch the pin updater from upstream-main commit selection to upstream stable release selection
- restore Yolo Update Pins as an hourly stable-release poller
- update maintainer and README docs to describe stable-release mirroring and the recovery lesson

Why:
- OpenClaw stable is already tag-based upstream, and nix-openclaw should mirror that release line directly
- this removes the old latest-green-main churn model and keeps source + app pins tied to one published upstream release

Tests:
- bash -n scripts/update-pins.sh
- GITHUB_ACTIONS=true GH_TOKEN="$(gh auth token)" scripts/update-pins.sh
2026-04-10 16:05:01 +02:00
joshp123
0d60673f02 fix(gateway-tests): mock provider and web-search plugin surfaces
Why:
- disabling bundled plugins globally fixed provider leakage but broke send tests that rely on bundled channels
- Linux gateway tests need bundled channels, but not real provider model augmentation or web search/fetch provider discovery

What:
- keep the gateway test env unchanged
- patch gateway test mocks to stub provider model augmentation to []
- patch gateway test mocks to stub runtime and public-artifact web search/fetch provider discovery to []
- keep the change in the Nix postpatch seam only

Tests:
- sh -n nix/scripts/gateway-postpatch.sh
- nix eval --raw --accept-flake-config .#checks.x86_64-linux.gateway-tests.src.outPath
2026-04-10 16:03:09 +02:00
joshp123
659c9f5973 chore: repin openclaw to v2026.4.9
Move nix-openclaw to the current OpenClaw daily release and refresh the
release-coupled generated artifacts.

Also fix the Nix build wrapper for A2UI bundling by running the same tsc +
rolldown steps directly instead of relying on upstream's nested pnpm runner,
which exits silently inside the Nix build environment.

Verification:
- nix build .#openclaw-gateway --accept-flake-config -L
- nix build .#checks.aarch64-darwin.ci --accept-flake-config -L
- scripts/hm-activation-macos.sh (expected local USER mismatch: josh vs runner)
2026-04-10 16:03:01 +02:00
joshp123
aa27809dc5 fix(nix): align the gateway build and test contract with upstream artifacts
What:
- harden the package build against broken prune symlinks and missing plugin metadata
- make the config-options and gateway test checks call the installed CLIs directly
- restore the bundled runtime-deps staging patch for Nix builds
- rebuild the test prebuild around the exact generated artifacts the Nix checks consume

Why:
- the release recovery failures were not random product regressions; they were Nix-owned build and test contract drift
- nix-openclaw needed the same plugin manifests, plugin-sdk artifacts, runtime deps staging, and local CLI resolution that upstream assumes

Tests:
- final green CI run on main: 24217118174
2026-04-10 16:02:51 +02:00
joshp123
88a4647384 fix(ci): move linux onto the larger blacksmith runner
What:
- move the Linux CI job to blacksmith-16vcpu-ubuntu-2404
- switch Linux Nix installation to cachix/install-nix-action

Why:
- the recovery line exhausted disk on ubuntu-latest during the Linux aggregator build
- the larger runner kept the normal parallel build graph intact and the cachix installer worked reliably there

Tests:
- final green CI run on main: 24217118174
2026-04-10 16:02:46 +02:00
joshp123
e97594cd50 ci: reset main onto clean recovery line
What:
- restore one visible CI workflow
- add CI concurrency and Linux failure-log dumping
- disable yolo during release recovery
- delete the old split workflow_run files

Why:
- cut away the post-reset churn immediately
- keep one readable contract on main
- prevent any updater mutation during recovery

Tests:
- git diff --check
2026-04-09 19:31:45 +02:00
openclaw-ci
64d4106668 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 16:38:06 +00:00
openclaw-ci
b915c422f4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 15:40:36 +00:00
openclaw-ci
990b572c3c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 13:49:56 +00:00
openclaw-ci
632bb133f6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 12:35:47 +00:00
openclaw-ci
d562894125 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 09:31:35 +00:00
openclaw-ci
2605856f77 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 08:29:54 +00:00
openclaw-ci
fd33ccd2bf 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 07:34:56 +00:00
openclaw-ci
f072f3714a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 03:17:40 +00:00
openclaw-ci
6481ec521d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 01:46:49 +00:00
openclaw-ci
8c1fd05c7a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-18 00:55:44 +00:00
openclaw-ci
feb5895dcc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 22:21:05 +00:00
openclaw-ci
c2aaf58d1d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 20:24:11 +00:00
openclaw-ci
082d03efcc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 19:35:00 +00:00
openclaw-ci
cf1486ed48 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 18:33:51 +00:00
openclaw-ci
734d2ac859 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 17:34:58 +00:00
openclaw-ci
f569001ded 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 16:38:23 +00:00
openclaw-ci
57df329849 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 15:36:14 +00:00
openclaw-ci
874649d990 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 14:39:08 +00:00
openclaw-ci
b918448180 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 12:35:03 +00:00
openclaw-ci
062945f265 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 11:28:01 +00:00
openclaw-ci
314c9d8e23 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 10:33:46 +00:00
openclaw-ci
439b5a67fd 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 10:08:22 +00:00
openclaw-ci
730adebd1e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 09:07:07 +00:00
openclaw-ci
87e7260d8e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 08:03:15 +00:00
openclaw-ci
4c107be1f6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 06:10:37 +00:00
openclaw-ci
d2d16be2af 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 05:04:45 +00:00
openclaw-ci
4b1b9fb299 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-17 02:54:22 +00:00
openclaw-ci
83fc5534a9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 21:56:17 +00:00
openclaw-ci
f17d8ba5b8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 20:56:05 +00:00
openclaw-ci
9f1084f76f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 17:17:55 +00:00
openclaw-ci
8cdfd30e0c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 15:16:26 +00:00
openclaw-ci
961ac0f06c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 13:51:52 +00:00
openclaw-ci
eb76f589ff 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 11:10:46 +00:00
openclaw-ci
dc8ec6f766 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 09:13:50 +00:00
openclaw-ci
e1dc63afd9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 05:57:36 +00:00
openclaw-ci
23978beac1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-16 03:35:07 +00:00
openclaw-ci
fd6a7306da 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 23:42:20 +00:00
openclaw-ci
373f89ba57 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 22:43:03 +00:00
openclaw-ci
4136401fbf 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 20:44:51 +00:00
openclaw-ci
e5c180f176 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 19:40:23 +00:00
openclaw-ci
c26b45793a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 16:48:17 +00:00
openclaw-ci
681d984288 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 14:44:55 +00:00
openclaw-ci
78c88f009c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 11:39:10 +00:00
openclaw-ci
3446aa3e22 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 09:49:20 +00:00
openclaw-ci
caf5755de9 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 08:55:10 +00:00
openclaw-ci
3d784abe7f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 07:14:40 +00:00
openclaw-ci
2d2dc144ad 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 05:44:59 +00:00
openclaw-ci
c984b3e393 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-15 03:30:13 +00:00
openclaw-ci
488260e0a8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 23:40:49 +00:00
openclaw-ci
7850eac853 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 21:40:13 +00:00
openclaw-ci
b16bde62d3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 19:39:30 +00:00
openclaw-ci
d6de9e5e67 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 18:57:45 +00:00
openclaw-ci
4c40ee0ac1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 17:41:00 +00:00
openclaw-ci
846246e91e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 14:43:47 +00:00
openclaw-ci
865c970afa 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 13:16:08 +00:00
openclaw-ci
99b5e15e5c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 11:37:26 +00:00
openclaw-ci
69e2d42fe0 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 10:44:00 +00:00
openclaw-ci
f40ededb24 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 08:51:58 +00:00
openclaw-ci
606418c4d5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 07:51:31 +00:00
openclaw-ci
4ef7776eb3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 07:06:19 +00:00
openclaw-ci
f1efd50ca7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 05:59:23 +00:00
openclaw-ci
19724e370d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 04:53:46 +00:00
openclaw-ci
7b621d7f29 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-14 02:50:36 +00:00
openclaw-ci
042aa82642 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 23:42:35 +00:00
openclaw-ci
9f98b82943 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 22:42:19 +00:00
openclaw-ci
36a8842472 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 21:47:31 +00:00
openclaw-ci
c7c1e5a9e7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 20:53:38 +00:00
openclaw-ci
e0120c3709 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 19:43:57 +00:00
openclaw-ci
8aa60d5a38 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 19:02:19 +00:00
openclaw-ci
df77061a7f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 17:51:08 +00:00
openclaw-ci
376aa32db0 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 16:57:13 +00:00
openclaw-ci
8ad43deaf8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 15:58:10 +00:00
openclaw-ci
a2bfe2a626 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 14:59:40 +00:00
openclaw-ci
b49874ed1e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 13:24:40 +00:00
openclaw-ci
bd37c77e0c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 11:43:29 +00:00
openclaw-ci
e15b04e90f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 09:56:58 +00:00
openclaw-ci
aa3390b570 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 08:58:44 +00:00
openclaw-ci
4d218944d1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 07:12:16 +00:00
openclaw-ci
2e83020fd3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 06:02:53 +00:00
openclaw-ci
81d703a00a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 04:57:15 +00:00
openclaw-ci
41835c3b42 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-13 02:52:26 +00:00
openclaw-ci
f94eda6be7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 23:43:36 +00:00
openclaw-ci
36efa52624 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 22:41:38 +00:00
openclaw-ci
c52847004e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 21:48:24 +00:00
openclaw-ci
ff30440860 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 20:52:24 +00:00
openclaw-ci
7f6e664dab 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 19:09:57 +00:00
openclaw-ci
46d9d14d65 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 18:01:55 +00:00
openclaw-ci
269c5163df 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 16:13:30 +00:00
openclaw-ci
fd492c6933 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 15:05:34 +00:00
openclaw-ci
50decbe2b4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 13:26:45 +00:00
openclaw-ci
725bc6e0cf 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 11:42:25 +00:00
openclaw-ci
27872342d5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 10:57:43 +00:00
openclaw-ci
e0d7ad6426 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 10:04:11 +00:00
openclaw-ci
c52fc90c54 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 09:00:58 +00:00
openclaw-ci
b862d654b1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 07:13:11 +00:00
openclaw-ci
3ebbc65c55 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 06:04:42 +00:00
openclaw-ci
27a29df0a6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 04:59:44 +00:00
openclaw-ci
8fe456207b 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-12 02:54:15 +00:00
openclaw-ci
1b4bf09f8c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 23:40:09 +00:00
openclaw-ci
f1704422d3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 22:40:37 +00:00
openclaw-ci
764b4f827d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 21:46:32 +00:00
openclaw-ci
57253324d3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 20:49:28 +00:00
openclaw-ci
7dc847afc0 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 19:53:00 +00:00
openclaw-ci
e65690c856 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 19:09:10 +00:00
openclaw-ci
ae705b8880 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 18:02:29 +00:00
openclaw-ci
a3b71dfacf 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 17:07:55 +00:00
openclaw-ci
8eeb832796 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 15:03:35 +00:00
openclaw-ci
e701ee7e5f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 13:29:01 +00:00
openclaw-ci
d35904e4d8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 10:58:39 +00:00
openclaw-ci
2dc793a274 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 10:00:29 +00:00
openclaw-ci
93e9cfdc9a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 07:10:44 +00:00
openclaw-ci
4ec03e75d5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 06:02:33 +00:00
openclaw-ci
cec4a53fc4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-11 04:54:42 +00:00
openclaw-ci
b48016df09 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 23:44:30 +00:00
openclaw-ci
d27c6a1b3a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 22:43:36 +00:00
openclaw-ci
60f4d78459 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 21:41:35 +00:00
openclaw-ci
ad6400395b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 20:49:22 +00:00
openclaw-ci
10e6a0715d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 19:51:31 +00:00
openclaw-ci
71d409fe32 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 17:59:17 +00:00
openclaw-ci
81b7e6811b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 15:11:41 +00:00
openclaw-ci
9bc3b14186 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 13:26:36 +00:00
openclaw-ci
97b539d4f4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 11:45:18 +00:00
openclaw-ci
dfafd6cc5b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 10:00:49 +00:00
openclaw-ci
a29d1420c2 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 07:54:44 +00:00
openclaw-ci
e56d012657 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 07:07:58 +00:00
openclaw-ci
133c9060ff 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 05:59:47 +00:00
openclaw-ci
08338f0f69 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 04:53:45 +00:00
openclaw-ci
7be64cd9c9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-10 02:50:26 +00:00
openclaw-ci
ebee5d2352 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 23:41:24 +00:00
openclaw-ci
48e091da9e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 22:44:25 +00:00
openclaw-ci
2e4b0529e3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 20:51:15 +00:00
openclaw-ci
34d4647871 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 19:52:40 +00:00
openclaw-ci
75c9417ab8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 19:09:19 +00:00
openclaw-ci
0547b7c517 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 16:13:27 +00:00
openclaw-ci
e12950b0f3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 15:08:12 +00:00
openclaw-ci
ad519160df 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 13:28:43 +00:00
openclaw-ci
43bd8eef38 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 11:44:49 +00:00
openclaw-ci
b39fc7f507 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 09:01:34 +00:00
openclaw-ci
6d6f93c179 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 07:59:54 +00:00
openclaw-ci
7251d3f74a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 06:10:01 +00:00
openclaw-ci
0d3ded9603 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 05:05:04 +00:00
openclaw-ci
4dc61b2cb9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-09 02:58:04 +00:00
openclaw-ci
d1f651b30f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 23:37:49 +00:00
openclaw-ci
22a66d212b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 19:36:13 +00:00
openclaw-ci
2b813ad781 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 18:53:17 +00:00
openclaw-ci
26d88d3d1c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 17:37:31 +00:00
openclaw-ci
d68a479958 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 16:44:25 +00:00
openclaw-ci
ab67e92005 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 15:38:20 +00:00
openclaw-ci
4b92cd6103 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 14:39:24 +00:00
openclaw-ci
cdde387df9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 11:34:38 +00:00
openclaw-ci
20e73c6312 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 10:40:27 +00:00
openclaw-ci
05dde40234 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 09:42:46 +00:00
openclaw-ci
9c9f76f958 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 08:48:08 +00:00
openclaw-ci
8c42ac7102 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 07:00:43 +00:00
openclaw-ci
ae8199dc7b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 05:57:02 +00:00
openclaw-ci
de1e28f877 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 04:55:58 +00:00
openclaw-ci
a4cdb6afd6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-08 02:56:56 +00:00
openclaw-ci
9249523f63 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 23:36:41 +00:00
openclaw-ci
ec5ff4a24b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 21:34:40 +00:00
openclaw-ci
d1d843badb 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 20:40:05 +00:00
openclaw-ci
e0d47bbb75 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 19:34:19 +00:00
openclaw-ci
c0429e372a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 18:52:01 +00:00
openclaw-ci
8395ff3097 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 17:35:35 +00:00
openclaw-ci
6f172e3f9f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 16:43:23 +00:00
openclaw-ci
045c07a14c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 15:37:02 +00:00
openclaw-ci
885e0de37a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 14:38:25 +00:00
openclaw-ci
0cab5f9ab5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 13:10:01 +00:00
openclaw-ci
a9468871d5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 11:34:05 +00:00
openclaw-ci
800cb6c690 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 10:38:56 +00:00
openclaw-ci
4694b4e54b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 09:42:34 +00:00
openclaw-ci
afc2a81019 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 07:41:08 +00:00
openclaw-ci
7240815dab 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 06:59:18 +00:00
openclaw-ci
03bb947aff 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 05:50:06 +00:00
openclaw-ci
69916b2ebc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 04:41:46 +00:00
openclaw-ci
f704240b5f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-07 02:44:10 +00:00
openclaw-ci
aee4e0558d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 23:40:16 +00:00
openclaw-ci
8141468385 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 22:44:26 +00:00
openclaw-ci
baf7895238 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 21:42:02 +00:00
openclaw-ci
2ad3fae24b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 20:48:02 +00:00
openclaw-ci
f5d79f4905 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 19:44:37 +00:00
openclaw-ci
e8a3ca7231 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 19:03:41 +00:00
openclaw-ci
73d3a07f64 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 17:51:06 +00:00
openclaw-ci
5ed5853798 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 16:59:38 +00:00
openclaw-ci
b5a994be61 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 15:53:41 +00:00
openclaw-ci
2b65c57f04 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 14:54:15 +00:00
openclaw-ci
92c89ebe88 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 13:21:01 +00:00
openclaw-ci
bd00261cbc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 10:54:06 +00:00
openclaw-ci
b7e1fd7df4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 09:56:23 +00:00
openclaw-ci
c98d5f0925 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 08:56:11 +00:00
openclaw-ci
cc3dd647e4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 07:07:17 +00:00
openclaw-ci
df7f1919c7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 05:58:59 +00:00
openclaw-ci
89b9b6895b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 04:53:12 +00:00
openclaw-ci
58c4cae97c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-06 02:50:09 +00:00
openclaw-ci
8946ea7007 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 23:16:48 +00:00
openclaw-ci
1f8f49b275 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 21:51:28 +00:00
openclaw-ci
4902f1a66f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 20:53:43 +00:00
openclaw-ci
a4b2dba828 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 20:09:18 +00:00
openclaw-ci
faa5c60c7f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 16:02:35 +00:00
openclaw-ci
9f2e652177 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 15:02:22 +00:00
openclaw-ci
d0438c203e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 13:25:43 +00:00
openclaw-ci
a8bed29f98 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 10:58:19 +00:00
openclaw-ci
97001f8789 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 10:01:01 +00:00
openclaw-ci
a80fbbe1ac 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 09:00:07 +00:00
openclaw-ci
9d3e92376c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 07:09:14 +00:00
openclaw-ci
b95a2c2bbf 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 06:01:10 +00:00
openclaw-ci
8b3b01f720 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 04:55:53 +00:00
openclaw-ci
cb061e5346 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-05 02:53:01 +00:00
openclaw-ci
10e4d73318 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 23:43:12 +00:00
openclaw-ci
febe56e66b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 21:50:26 +00:00
openclaw-ci
948085aee3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 20:47:07 +00:00
openclaw-ci
efb416770d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 19:07:05 +00:00
openclaw-ci
e54b4a72eb 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 17:03:06 +00:00
openclaw-ci
17055a419a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 14:54:02 +00:00
openclaw-ci
584ad1556a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 13:21:54 +00:00
openclaw-ci
203ee30747 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 11:43:27 +00:00
openclaw-ci
d6b17b3462 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 10:54:31 +00:00
openclaw-ci
77f95b2d31 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 09:57:57 +00:00
openclaw-ci
be03e9c1f1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 07:51:55 +00:00
openclaw-ci
dafc8ce959 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 07:06:03 +00:00
openclaw-ci
6844be5659 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 05:59:10 +00:00
openclaw-ci
91388017b5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 04:52:11 +00:00
openclaw-ci
2d5b49dffe 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-04 02:49:56 +00:00
openclaw-ci
146be916d6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 23:40:34 +00:00
openclaw-ci
c87ec7a5b7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 21:46:42 +00:00
openclaw-ci
c005cf946b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 19:51:49 +00:00
openclaw-ci
76068fdf8e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 17:57:39 +00:00
openclaw-ci
76c0804664 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 17:05:39 +00:00
openclaw-ci
8acd74a46b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 15:59:24 +00:00
openclaw-ci
85888390e6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 15:01:38 +00:00
openclaw-ci
c006693ce5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 13:22:49 +00:00
openclaw-ci
8c8bae02d1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 11:44:42 +00:00
openclaw-ci
8e25eef3f1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 10:57:19 +00:00
openclaw-ci
bf98cf6573 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 10:00:45 +00:00
openclaw-ci
31efb28909 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 07:55:41 +00:00
openclaw-ci
e1011d51af 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 07:09:31 +00:00
openclaw-ci
8efac106b7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 06:02:05 +00:00
openclaw-ci
b227ebbaf6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 04:58:16 +00:00
openclaw-ci
e9225f59cb 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-03 02:57:19 +00:00
openclaw-ci
3460fa878a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 23:40:03 +00:00
openclaw-ci
c0e79a5d05 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 22:42:16 +00:00
openclaw-ci
c07498701c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 21:48:18 +00:00
openclaw-ci
5f038b79cb 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 20:52:23 +00:00
openclaw-ci
12fbd5361e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 19:49:03 +00:00
openclaw-ci
1d142fc533 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 19:06:11 +00:00
openclaw-ci
8571d76667 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 17:58:33 +00:00
openclaw-ci
11c2839883 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 17:05:04 +00:00
openclaw-ci
a091baebe8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 15:55:43 +00:00
openclaw-ci
9c1b2d4ad1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 14:59:20 +00:00
openclaw-ci
f2bbd0d960 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 13:24:13 +00:00
openclaw-ci
10b2ed77d6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 11:41:54 +00:00
openclaw-ci
0d8e9e7d72 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 11:00:41 +00:00
openclaw-ci
943b983c1f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 10:05:15 +00:00
openclaw-ci
90b7a77c87 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 09:01:04 +00:00
openclaw-ci
c884a16f00 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 07:14:57 +00:00
openclaw-ci
7440567bd1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 06:07:20 +00:00
openclaw-ci
3027087818 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 04:59:52 +00:00
openclaw-ci
527d46df34 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-02 02:53:55 +00:00
openclaw-ci
d3a67150f7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 23:36:51 +00:00
openclaw-ci
abd7c5ff0e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 22:38:50 +00:00
openclaw-ci
e807eb4b77 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 21:36:13 +00:00
openclaw-ci
ce85b55569 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 20:41:14 +00:00
openclaw-ci
eff22020f0 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 19:34:09 +00:00
openclaw-ci
594f88ccfb 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 18:52:05 +00:00
openclaw-ci
4ba6319ebc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 17:36:25 +00:00
openclaw-ci
4d9eb961cd 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 16:43:28 +00:00
openclaw-ci
ce56cc5881 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 15:36:27 +00:00
openclaw-ci
c746965646 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 14:38:16 +00:00
openclaw-ci
aa661f72a6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 13:10:30 +00:00
openclaw-ci
c2623871c8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 11:32:06 +00:00
openclaw-ci
9f564ea4b7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 10:39:51 +00:00
openclaw-ci
626e600679 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 09:42:43 +00:00
openclaw-ci
80373ef96a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 07:02:05 +00:00
openclaw-ci
2b1e71bd01 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 05:25:26 +00:00
openclaw-ci
397cec55c1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-03-01 03:23:40 +00:00
openclaw-ci
74cdab9527 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 23:34:53 +00:00
openclaw-ci
260393e73a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 22:37:19 +00:00
openclaw-ci
16f1167066 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 21:34:33 +00:00
openclaw-ci
00c4d73a26 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 20:39:24 +00:00
openclaw-ci
69d42f0167 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 18:50:23 +00:00
openclaw-ci
9d3474c8e0 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 17:33:37 +00:00
openclaw-ci
f00513c30e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 16:41:04 +00:00
openclaw-ci
a6cc274260 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 15:35:55 +00:00
openclaw-ci
7283f62107 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 13:07:30 +00:00
openclaw-ci
382fd80a66 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 10:38:30 +00:00
openclaw-ci
e7756a7cd8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 09:39:19 +00:00
openclaw-ci
2e7328edcb 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 08:43:46 +00:00
openclaw-ci
55310808f3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 07:38:45 +00:00
openclaw-ci
edd7380c68 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 06:56:46 +00:00
openclaw-ci
8ac6079fb7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 05:44:08 +00:00
openclaw-ci
5cfa93a076 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 04:24:40 +00:00
openclaw-ci
d6c5bdc12e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-28 02:39:40 +00:00
openclaw-ci
565507643b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 23:37:58 +00:00
openclaw-ci
6e9bc40eec 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 22:38:16 +00:00
openclaw-ci
6e34f3f3ba 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 21:41:28 +00:00
openclaw-ci
018e1e8ace 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 19:43:43 +00:00
openclaw-ci
531151e34d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 19:01:57 +00:00
openclaw-ci
f95429513f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 17:51:28 +00:00
openclaw-ci
0dd419c2a9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 16:56:26 +00:00
openclaw-ci
16d12d14b6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 15:51:54 +00:00
openclaw-ci
5e5d28a695 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 14:55:12 +00:00
openclaw-ci
f37a4e184d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 13:22:42 +00:00
openclaw-ci
e9a99f60dc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 10:55:34 +00:00
openclaw-ci
ceffcf45ee 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 09:58:47 +00:00
openclaw-ci
84a606a1c8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 08:58:36 +00:00
openclaw-ci
283cfaeaca 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 07:10:00 +00:00
openclaw-ci
9127e02698 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 06:03:52 +00:00
openclaw-ci
addf0a03c4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 04:55:52 +00:00
openclaw-ci
ef468a7de2 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-27 02:57:47 +00:00
openclaw-ci
b967152466 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 23:42:45 +00:00
openclaw-ci
7b7dd0e001 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 22:49:51 +00:00
openclaw-ci
849e8d4bf2 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 21:46:25 +00:00
openclaw-ci
3e7b845582 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 20:54:10 +00:00
openclaw-ci
92d380cb75 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 19:54:43 +00:00
openclaw-ci
8b4f86bf36 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 19:08:41 +00:00
openclaw-ci
475e6986a7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 17:15:15 +00:00
openclaw-ci
554e09563d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 16:05:00 +00:00
openclaw-ci
7b3b7590dc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 15:11:34 +00:00
joshp123
15a35bc58d fix(ci): fail stale yolo runs and unblock offline A2UI build
What:
- export node_modules/.pnpm/node_modules/.bin in gateway build before canvas:a2ui:bundle
  so rolldown is found in sandbox/offline builds
- track openclaw bump failure in scripts/update-pins.sh and fail the workflow when
  openclaw upstream is ahead but no openclaw pin update was produced

Why:
- yolo was reporting success while silently restoring old pins
- openclaw bump attempts were repeatedly failing at A2UI bundling, keeping pins stale

Tests:
- bash -n scripts/update-pins.sh
- bash -n nix/scripts/gateway-build.sh
2026-02-26 16:07:31 +01:00
openclaw-ci
b1be22b2d0 🤖 codex: bump nix-steipete-tools
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-26 13:59:28 +00:00
openclaw-ci
fbef208719 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-21 07:01:58 +00:00
openclaw-ci
494dcc360c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-21 05:55:00 +00:00
openclaw-ci
8ff0e2582e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-21 04:47:16 +00:00
openclaw-ci
820d0709fa 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-21 02:46:53 +00:00
openclaw-ci
ba0b84b482 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 23:41:05 +00:00
openclaw-ci
d963ce954c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 21:38:16 +00:00
openclaw-ci
79071558e2 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 19:44:19 +00:00
openclaw-ci
eabd8deab9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 19:04:53 +00:00
openclaw-ci
dccd3cebf0 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 17:52:56 +00:00
openclaw-ci
8ac857d730 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 16:57:22 +00:00
openclaw-ci
b6dcb57f95 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 14:55:59 +00:00
openclaw-ci
0e94d92dbd 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 13:22:59 +00:00
openclaw-ci
a01fe9acff 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 10:54:55 +00:00
openclaw-ci
ce4387e90a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 08:57:32 +00:00
openclaw-ci
4680ec6cd1 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 07:14:00 +00:00
openclaw-ci
0f2e923094 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 06:05:47 +00:00
openclaw-ci
71fd6f4534 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 04:58:16 +00:00
openclaw-ci
d685b699f3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-20 02:53:10 +00:00
openclaw-ci
41824bcf9d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 23:43:23 +00:00
openclaw-ci
cd109e2b54 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 22:47:56 +00:00
openclaw-ci
9165d0228d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 21:46:17 +00:00
openclaw-ci
91bf78a135 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 20:47:31 +00:00
openclaw-ci
c6cc749128 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 19:49:53 +00:00
openclaw-ci
de2fef944e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 19:08:07 +00:00
openclaw-ci
837ae2d0d7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 17:09:19 +00:00
openclaw-ci
fe4bd06b3a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 11:51:41 +00:00
openclaw-ci
b682bf6c07 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 11:00:40 +00:00
openclaw-ci
31ded5c1b3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 10:04:28 +00:00
openclaw-ci
e55458cde4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 09:01:18 +00:00
openclaw-ci
61c93964cd 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 08:02:10 +00:00
openclaw-ci
444013a9b3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 06:15:19 +00:00
openclaw-ci
ac653c1396 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 05:05:24 +00:00
openclaw-ci
0807d61c10 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-19 02:58:27 +00:00
openclaw-ci
fd20aa20df 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 23:42:48 +00:00
openclaw-ci
2dfbeee333 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 22:49:20 +00:00
openclaw-ci
3fd87e299b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 21:49:49 +00:00
openclaw-ci
d2f8af5880 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 20:57:31 +00:00
openclaw-ci
eacc1a26a4 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 19:12:06 +00:00
joshp123
49958c1902 🤖 chore: bump bundled steipete plugin source to locked commit
What:
- update bundled steipete source rev in openclaw HM module to c110209720cbc6c87fccb6c1e1c2b79b1d719245
- update corresponding narHash

Why:
- consume nix-steipete-tools commit that adds flake.lock for goplaces/gogcli subflakes
- remove downstream lockfile mutation warnings during consumer eval/deploy

Tests:
- nix eval --impure --raw .#packages.aarch64-darwin.openclaw.name
2026-02-18 09:26:37 -08:00
openclaw-ci
6d5d2c9b44 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 17:19:18 +00:00
openclaw-ci
e299edf13d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 16:05:44 +00:00
openclaw-ci
5a1d557c4b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 15:00:16 +00:00
openclaw-ci
a58fe5501b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 13:30:30 +00:00
openclaw-ci
4e82d13be5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 11:52:22 +00:00
openclaw-ci
0c569c2f13 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 11:00:47 +00:00
openclaw-ci
2f4c4c34a9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 10:03:43 +00:00
openclaw-ci
ee3df04b40 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 08:00:06 +00:00
openclaw-ci
e29a8cc562 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 06:14:03 +00:00
openclaw-ci
e6a527cc26 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 05:05:40 +00:00
joshp123
9c02944dd1 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 20:39:10 -08:00
joshp123
38edf67292 🤖 refactor: hard-break legacy plugin options and unify bundled catalog
What:
- remove legacy option migrations for programs.openclaw.firstParty/plugins
- add explicit removed-option failures pointing to bundledPlugins/customPlugins
- add plugin-catalog.nix as single source of truth for bundled plugins
- generate bundled option toggles, source map, linux check selection, and tool list from the catalog
- update docs/wording from first-party to bundled plugins

Why:
- enforce forward-only API changes with fail-fast errors
- eliminate duplicated plugin lists drifting across module/check/tool surfaces
- keep consumer configuration mental model simple: bundledPlugins + customPlugins only

Tests:
- nix flake check --no-build (pass)
2026-02-17 19:51:06 -08:00
openclaw-ci
d97f568377 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-18 02:58:54 +00:00
openclaw-ci
c93f90ad8a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 23:42:19 +00:00
openclaw-ci
2c7a1993b9 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 22:48:09 +00:00
openclaw-ci
c58eea515b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 21:48:11 +00:00
openclaw-ci
83ac049753 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 20:53:54 +00:00
openclaw-ci
6662afb55f 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 19:13:48 +00:00
openclaw-ci
4ad8f1df67 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 17:13:48 +00:00
openclaw-ci
d8407b7149 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 16:05:11 +00:00
openclaw-ci
a6dca87556 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 15:02:05 +00:00
openclaw-ci
e52747808e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 13:28:00 +00:00
openclaw-ci
7425e6770e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 11:52:06 +00:00
openclaw-ci
02a8e40d6d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 10:04:05 +00:00
openclaw-ci
0eac419399 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 09:02:04 +00:00
openclaw-ci
d5308d2a67 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 07:16:40 +00:00
openclaw-ci
63046dd763 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 06:10:01 +00:00
openclaw-ci
ba566add95 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 05:04:15 +00:00
openclaw-ci
f1138375d2 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-17 03:42:56 +00:00
openclaw-ci
e54a3afba4 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 22:48:26 +00:00
openclaw-ci
2cb3f41347 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 21:41:36 +00:00
openclaw-ci
ae799225b3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 20:45:09 +00:00
openclaw-ci
454e1549a0 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 19:42:11 +00:00
openclaw-ci
1d0c76d84c 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 19:04:46 +00:00
openclaw-ci
9a62be32e8 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 17:52:08 +00:00
openclaw-ci
669e836148 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 17:00:35 +00:00
openclaw-ci
a4a4e3ba11 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 15:55:24 +00:00
openclaw-ci
0d012a5022 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 14:57:46 +00:00
openclaw-ci
720b57138e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 13:37:45 +00:00
openclaw-ci
7551945933 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 11:07:59 +00:00
openclaw-ci
a39b769ac5 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 10:08:30 +00:00
openclaw-ci
62b43a822b 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 09:09:52 +00:00
openclaw-ci
f9151a988a 🤖 codex: bump nix-steipete-tools
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 07:32:10 +00:00
joshp123
8d7489b093 🤖 fix: bump openclaw pin to reasoning replay fix
What:
- update nix-openclaw openclaw source pin to openclaw merge commit 68ea0639
- refresh source hash for the new upstream revision

Why:
- propagate the OpenAI reasoning replay follower-id fix into nix-openclaw packages
- unblock downstream clawdinator roll-forward to the fixed openclaw build

Tests:
- nix build .#openclaw-gateway --accept-flake-config
2026-02-15 23:15:02 -08:00
joshp123
d1314e9c5c 🤖 nix: keep gateway speedups and drop cache-only workflow
What:
- set `dontFixup = true` for `openclaw-gateway`
- speed install phase by moving build outputs instead of deep-copying node_modules
- add lightweight dangling-symlink integrity check in gateway install script
- remove `.github/workflows/cache-only.yml`

Why:
- keep the high-value packaging/build speed improvements
- keep one minimal safety guard when fixup is skipped
- remove flaky cache orchestration that adds CI latency and merge-SHA timeout failures

Tests:
- not run locally (per-request: CI-only validation due local NixOS issues)
- will validate via GitHub Actions/Garnix on push to main
2026-02-15 21:12:21 -08:00
openclaw-ci
36a8b0fc6d 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 05:11:56 +00:00
openclaw-ci
e7c05cebf7 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-16 03:24:11 +00:00
openclaw-ci
f0625a67b3 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 23:40:11 +00:00
openclaw-ci
bfe94d5f1a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 22:44:50 +00:00
joshp123
2a9a3be47b nixos: add openclaw-gateway module
Adds services.openclaw-gateway NixOS module:
- systemd service for │
◇  Doctor warnings ──────────────────────────────────────╮
│                                                        │
│  - State dir migration skipped: target already exists  │
│    (/Users/josh/.openclaw). Remove or merge manually.  │
│                                                        │
├────────────────────────────────────────────────────────╯
- deep-merged config attrset -> /etc/openclaw/openclaw.json
- configurable unit name, env, preStart, and PATH

This lets downstream stacks (clawdinators) avoid re-implementing the gateway service.
2026-02-15 14:32:00 -08:00
joshp123
0eac830c65 chore: profile gateway build (break down pnpm build steps) 2026-02-15 14:12:16 -08:00
openclaw-ci
57bafcd598 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 21:38:58 +00:00
openclaw-ci
e28e35b1fc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 20:43:04 +00:00
openclaw-ci
4422e0b767 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 19:39:13 +00:00
joshp123
3feb491321 ci: aggregate checks to avoid duplicate gateway builds 2026-02-15 11:03:07 -08:00
openclaw-ci
1a6753a1c6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 18:55:10 +00:00
joshp123
e01b23c2a5 chore: add build step timings + minor build/install tweaks 2026-02-15 10:26:27 -08:00
openclaw-ci
3cb21cd49e 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 15:44:55 +00:00
openclaw-ci
06aeac8fbc 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 14:42:31 +00:00
openclaw-ci
5e4c8c1e1a 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 13:15:24 +00:00
openclaw-ci
3993c50689 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 09:46:06 +00:00
joshp123
b469a3ce73 perf: speed up gateway builds (shared pnpm deps, fetcher v3, targeted rebuild) 2026-02-15 00:31:28 -08:00
openclaw-ci
a05a1ed7d6 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 07:49:35 +00:00
joshp123
c3d3be60ac style: nix fmt (nixfmt-tree) + exclude generated config options 2026-02-14 23:24:32 -08:00
joshp123
f8681411dd refactor: share gateway build plumbing + stamp config-options provenance 2026-02-14 23:24:31 -08:00
openclaw-ci
4d999080ca 🤖 codex: bump openclaw pins
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 07:07:20 +00:00
joshp123
585ce70684 chore: add nix fmt formatter + switch to fetchPnpmDeps 2026-02-14 22:05:31 -08:00
openclaw-ci
255e3a6c26 🤖 codex: bump pins (tools + openclaw)
What:
- update pinned inputs/pkgs for nix-openclaw (best-effort)

Why:
- keep the flake fresh automatically

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config (darwin only)
2026-02-15 05:34:28 +00:00
joshp123
309fa7a19c chore: derive gateway/check versions from pinned rev; dedupe node-addon-api 2026-02-14 21:01:47 -08:00
joshp123
807a6563ab chore: make update-pins best-effort for tools vs openclaw 2026-02-14 20:13:41 -08:00
openclaw-ci
772be3e113 🤖 codex: bump openclaw pins (no-issue)
What:
- pin openclaw source to latest upstream main
- refresh macOS app pin to latest release asset
- update source and app hashes
- regenerate config options from upstream schema

Why:
- keep nix-openclaw on latest upstream for yolo mode

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config
2026-02-15 03:21:27 +00:00
joshp123
b94a14db48 chore: bump nix-steipete-tools (goplaces v0.3.0) 2026-02-14 19:16:22 -08:00
joshp123
e98f3f9e74 chore: bump nix-steipete-tools 2026-02-14 19:05:08 -08:00
openclaw-ci
c5036d67bc 🤖 codex: bump openclaw pins (no-issue)
What:
- pin openclaw source to latest upstream main
- refresh macOS app pin to latest release asset
- update source and app hashes
- regenerate config options from upstream schema

Why:
- keep nix-openclaw on latest upstream for yolo mode

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config
2026-02-15 00:01:54 +00:00
openclaw-ci
f105871a9c 🤖 codex: bump openclaw pins (no-issue)
What:
- pin openclaw source to latest upstream main
- refresh macOS app pin to latest release asset
- update source and app hashes
- regenerate config options from upstream schema

Why:
- keep nix-openclaw on latest upstream for yolo mode

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config
2026-02-14 23:06:57 +00:00
openclaw-ci
8c206a611d 🤖 codex: bump openclaw pins (no-issue)
What:
- pin openclaw source to latest upstream main
- refresh macOS app pin to latest release asset
- update source and app hashes
- regenerate config options from upstream schema

Why:
- keep nix-openclaw on latest upstream for yolo mode

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config
2026-02-14 21:39:45 +00:00
openclaw-ci
56792757b0 🤖 codex: bump openclaw pins (no-issue)
What:
- pin openclaw source to latest upstream main
- refresh macOS app pin to latest release asset
- update source and app hashes
- regenerate config options from upstream schema

Why:
- keep nix-openclaw on latest upstream for yolo mode

Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config
2026-02-14 20:53:12 +00:00
joshp123
2b3c43d1d0 revert: pause cachix migration; restore garnix CI 2026-02-14 11:38:43 -08:00
100 changed files with 14111 additions and 7388 deletions

View File

@ -1,82 +0,0 @@
name: Cache Only
on:
workflow_run:
workflows: [ "Nix Build Cache" ]
types: [ completed ]
workflow_dispatch: {}
jobs:
cache-only:
if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') }}
runs-on: ubuntu-latest
env:
TARGET_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
CACHIX_CACHE_NAME: ${{ vars.CACHIX_CACHE_NAME }}
WAIT_MINUTES: 45
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ env.TARGET_SHA }}
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
- name: Setup Cachix (read-only)
if: ${{ vars.CACHIX_CACHE_NAME != '' }}
uses: cachix/cachix-action@v15
with:
name: ${{ vars.CACHIX_CACHE_NAME }}
- name: Verify Cachix has required outputs
run: |
set -euo pipefail
if [ -z "${CACHIX_CACHE_NAME:-}" ]; then
echo "vars.CACHIX_CACHE_NAME not set; skipping cache verification."
exit 0
fi
STORE_URL="https://${CACHIX_CACHE_NAME}.cachix.org"
echo "Checking cache: ${STORE_URL}"
targets=(
packages.aarch64-darwin.openclaw
packages.aarch64-darwin.openclaw-gateway
packages.aarch64-darwin.openclaw-tools
packages.aarch64-darwin.openclaw-app
packages.x86_64-linux.openclaw
packages.x86_64-linux.openclaw-gateway
packages.x86_64-linux.openclaw-tools
checks.aarch64-darwin.gateway
checks.x86_64-linux.gateway
checks.x86_64-linux.gateway-tests
checks.x86_64-linux.config-options
)
deadline=$(( $(date +%s) + WAIT_MINUTES * 60 ))
while true; do
missing=()
for target in "${targets[@]}"; do
out_path=$(nix --extra-experimental-features 'nix-command flakes' eval --accept-flake-config --raw ".#${target}.outPath")
if ! nix path-info --store "$STORE_URL" "$out_path" >/dev/null 2>&1; then
missing+=("${target}:${out_path}")
fi
done
if [ ${#missing[@]} -eq 0 ]; then
echo "Cache ready on ${STORE_URL}."
break
fi
if [ "$(date +%s)" -gt "$deadline" ]; then
echo "Cache missing after ${WAIT_MINUTES} minutes:"
printf ' - %s\n' "${missing[@]}"
exit 1
fi
echo "Cache still missing (${#missing[@]}). Retrying in 30s..."
sleep 30
done

52
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: CI
on:
pull_request:
push:
branches: [ main ]
workflow_dispatch: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
linux:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Verify flake.lock owners
run: scripts/check-flake-lock-owners.sh
- name: Install Nix
uses: cachix/install-nix-action@v31
- name: Run Linux CI aggregator
run: timeout --foreground 50m nix build .#checks.x86_64-linux.ci --accept-flake-config
- name: Dump failing source check log
if: failure()
run: |
drv="$(nix eval --raw .#checks.x86_64-linux.source-checks.drvPath --accept-flake-config)"
nix log "$drv" | tail -n 400 || true
macos:
runs-on: macos-14
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v22
- name: Build Darwin CI aggregator
timeout-minutes: 25
run: nix build .#checks.aarch64-darwin.ci --accept-flake-config
- name: Run HM activation
timeout-minutes: 10
run: scripts/hm-activation-macos.sh

View File

@ -1,30 +0,0 @@
name: Config Options Guard
on:
pull_request:
push:
branches: [ main ]
workflow_run:
workflows: [ "Yolo Update Pins" ]
types: [ completed ]
jobs:
config-options:
if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') }}
runs-on: ubuntu-latest
env:
TARGET_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ env.TARGET_SHA }}
- name: Verify flake.lock owners
run: scripts/check-flake-lock-owners.sh
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
- name: Verify config options are up to date
run: nix build .#checks.x86_64-linux.config-options --print-build-logs

View File

@ -1,27 +0,0 @@
name: HM Activation (Linux)
on:
pull_request:
push:
branches: [ main ]
workflow_run:
workflows: [ "Yolo Update Pins" ]
types: [ completed ]
jobs:
hm-activation-linux:
if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') }}
runs-on: ubuntu-latest
env:
TARGET_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ env.TARGET_SHA }}
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
- name: Run HM activation
run: nix build .#checks.x86_64-linux.hm-activation --print-build-logs

View File

@ -1,27 +0,0 @@
name: HM Activation (macOS)
on:
pull_request:
push:
branches: [ main ]
workflow_run:
workflows: [ "Yolo Update Pins" ]
types: [ completed ]
jobs:
hm-activation-macos:
if: ${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') }}
runs-on: macos-14
env:
TARGET_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ env.TARGET_SHA }}
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
- name: Run HM activation
run: scripts/hm-activation-macos.sh

View File

@ -1,91 +0,0 @@
name: Nix Build Cache
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
build-linux:
runs-on: ubuntu-latest
env:
CACHIX_CACHE_NAME: ${{ vars.CACHIX_CACHE_NAME }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
- name: Setup Cachix
if: ${{ vars.CACHIX_CACHE_NAME != '' }}
uses: cachix/cachix-action@v15
with:
name: ${{ vars.CACHIX_CACHE_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build (and push if configured)
run: |
set -euo pipefail
targets=(
.#packages.x86_64-linux.openclaw
.#packages.x86_64-linux.openclaw-gateway
.#packages.x86_64-linux.openclaw-tools
.#checks.x86_64-linux.gateway
.#checks.x86_64-linux.gateway-tests
.#checks.x86_64-linux.config-options
.#checks.x86_64-linux.default-instance
.#checks.x86_64-linux.config-validity
.#checks.x86_64-linux.package-contents
)
if [ -n "${CACHIX_CACHE_NAME:-}" ] && [ -n "${CACHIX_AUTH_TOKEN:-}" ]; then
echo "Building + pushing to Cachix cache: ${CACHIX_CACHE_NAME}"
cachix watch-exec "${CACHIX_CACHE_NAME}" -- \
nix build --accept-flake-config --print-build-logs "${targets[@]}"
else
echo "Building without Cachix push (cache not configured)."
nix build --accept-flake-config --print-build-logs "${targets[@]}"
fi
build-macos:
runs-on: macos-14
env:
CACHIX_CACHE_NAME: ${{ vars.CACHIX_CACHE_NAME }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
- name: Setup Cachix
if: ${{ vars.CACHIX_CACHE_NAME != '' }}
uses: cachix/cachix-action@v15
with:
name: ${{ vars.CACHIX_CACHE_NAME }}
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Build (and push if configured)
run: |
set -euo pipefail
targets=(
.#packages.aarch64-darwin.openclaw
.#packages.aarch64-darwin.openclaw-gateway
.#packages.aarch64-darwin.openclaw-tools
.#packages.aarch64-darwin.openclaw-app
.#checks.aarch64-darwin.gateway
)
if [ -n "${CACHIX_CACHE_NAME:-}" ] && [ -n "${CACHIX_AUTH_TOKEN:-}" ]; then
echo "Building + pushing to Cachix cache: ${CACHIX_CACHE_NAME}"
cachix watch-exec "${CACHIX_CACHE_NAME}" -- \
nix build --accept-flake-config --print-build-logs "${targets[@]}"
else
echo "Building without Cachix push (cache not configured)."
nix build --accept-flake-config --print-build-logs "${targets[@]}"
fi

View File

@ -5,25 +5,253 @@ on:
- cron: "5 * * * *"
workflow_dispatch: {}
concurrency:
group: yolo-update-pins
cancel-in-progress: false
permissions:
contents: write
contents: read
jobs:
update:
select:
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
has_update: ${{ steps.select.outputs.has_update }}
source_tag: ${{ steps.select.outputs.source_tag }}
source_sha: ${{ steps.select.outputs.source_sha }}
source_version: ${{ steps.select.outputs.source_version }}
app_tag: ${{ steps.select.outputs.app_tag }}
app_url: ${{ steps.select.outputs.app_url }}
app_version: ${{ steps.select.outputs.app_version }}
latest_stable_tag: ${{ steps.select.outputs.latest_stable_tag }}
app_lag_releases: ${{ steps.select.outputs.app_lag_releases }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Select release
id: select
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
selection="$(scripts/update-pins.sh select)"
while IFS='=' read -r key value; do
echo "${key}=${value}" >> "$GITHUB_OUTPUT"
done <<<"$selection"
has_update="$(printf '%s\n' "$selection" | awk -F= '$1 == "has_update" { print $2; exit }')"
source_tag="$(printf '%s\n' "$selection" | awk -F= '$1 == "source_tag" { print $2; exit }')"
app_tag="$(printf '%s\n' "$selection" | awk -F= '$1 == "app_tag" { print $2; exit }')"
latest_stable_tag="$(printf '%s\n' "$selection" | awk -F= '$1 == "latest_stable_tag" { print $2; exit }')"
app_lag_releases="$(printf '%s\n' "$selection" | awk -F= '$1 == "app_lag_releases" { print $2; exit }')"
{
echo "### OpenClaw release selection"
echo
echo "- Latest stable upstream release: \`${latest_stable_tag:-unknown}\`"
echo "- Selected source release: \`${source_tag:-unknown}\`"
echo "- Selected macOS app artifact: \`${app_tag:-preserve-current}\`"
echo "- Update needed: \`${has_update:-unknown}\`"
if [[ -n "${app_lag_releases:-}" ]]; then
echo "- macOS app asset lagging source release(s): \`${app_lag_releases}\`"
fi
} >> "$GITHUB_STEP_SUMMARY"
validate-linux:
needs: select
if: needs.select.outputs.has_update == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
outputs:
materialization_digest: ${{ steps.materialization.outputs.materialization_digest }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Nix
uses: cachix/install-nix-action@v31
- name: Materialize selected release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
scripts/update-pins.sh apply \
"${{ needs.select.outputs.source_tag }}" \
"${{ needs.select.outputs.source_sha }}" \
"${{ needs.select.outputs.app_tag }}" \
"${{ needs.select.outputs.app_url }}"
- name: Record materialized diff digest
id: materialization
run: |
set -euo pipefail
digest="$(
git diff --binary -- \
nix/sources/openclaw-source.nix \
nix/packages/openclaw-app.nix \
nix/generated/openclaw-config-options.nix \
| shasum -a 256 \
| awk '{ print $1 }'
)"
echo "materialization_digest=${digest}" >> "$GITHUB_OUTPUT"
- name: Verify flake.lock owners
run: scripts/check-flake-lock-owners.sh
- name: Run Linux CI aggregator
run: timeout --foreground 50m nix build .#checks.x86_64-linux.ci --accept-flake-config
- name: Dump failing source check log
if: failure()
run: |
drv="$(nix eval --raw .#checks.x86_64-linux.source-checks.drvPath --accept-flake-config)"
nix log "$drv" | tail -n 400 || true
validate-macos:
needs: select
if: needs.select.outputs.has_update == 'true'
runs-on: macos-14
timeout-minutes: 40
outputs:
materialization_digest: ${{ steps.materialization.outputs.materialization_digest }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v22
- name: Materialize selected release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
scripts/update-pins.sh apply \
"${{ needs.select.outputs.source_tag }}" \
"${{ needs.select.outputs.source_sha }}" \
"${{ needs.select.outputs.app_tag }}" \
"${{ needs.select.outputs.app_url }}"
- name: Record materialized diff digest
id: materialization
run: |
set -euo pipefail
digest="$(
git diff --binary -- \
nix/sources/openclaw-source.nix \
nix/packages/openclaw-app.nix \
nix/generated/openclaw-config-options.nix \
| shasum -a 256 \
| awk '{ print $1 }'
)"
echo "materialization_digest=${digest}" >> "$GITHUB_OUTPUT"
- name: Build Darwin CI aggregator
timeout-minutes: 25
run: nix build .#checks.aarch64-darwin.ci --accept-flake-config
- name: Run HM activation
timeout-minutes: 10
run: scripts/hm-activation-macos.sh
promote:
needs:
- select
- validate-linux
- validate-macos
if: needs.select.outputs.has_update == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
actions: write
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
uses: DeterminateSystems/nix-installer-action@v22
- name: Run updater
- name: Promote selected release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LINUX_MATERIALIZATION_DIGEST: ${{ needs.validate-linux.outputs.materialization_digest }}
MACOS_MATERIALIZATION_DIGEST: ${{ needs.validate-macos.outputs.materialization_digest }}
run: |
set -euo pipefail
git config user.name "openclaw-ci"
git config user.email "ci@openclaw.local"
scripts/update-pins.sh
if [[ -z "$LINUX_MATERIALIZATION_DIGEST" || -z "$MACOS_MATERIALIZATION_DIGEST" ]]; then
echo "Missing validation materialization digest." >&2
exit 1
fi
if [[ "$LINUX_MATERIALIZATION_DIGEST" != "$MACOS_MATERIALIZATION_DIGEST" ]]; then
echo "Linux and macOS materialized different release diffs." >&2
echo "Linux: $LINUX_MATERIALIZATION_DIGEST" >&2
echo "macOS: $MACOS_MATERIALIZATION_DIGEST" >&2
exit 1
fi
scripts/update-pins.sh apply \
"${{ needs.select.outputs.source_tag }}" \
"${{ needs.select.outputs.source_sha }}" \
"${{ needs.select.outputs.app_tag }}" \
"${{ needs.select.outputs.app_url }}"
if git diff --quiet -- \
nix/sources/openclaw-source.nix \
nix/packages/openclaw-app.nix \
nix/generated/openclaw-config-options.nix; then
echo "No pin changes detected."
exit 0
fi
promote_digest="$(
git diff --binary -- \
nix/sources/openclaw-source.nix \
nix/packages/openclaw-app.nix \
nix/generated/openclaw-config-options.nix \
| shasum -a 256 \
| awk '{ print $1 }'
)"
if [[ "$promote_digest" != "$LINUX_MATERIALIZATION_DIGEST" ]]; then
echo "Promote materialized a different release diff than validation." >&2
echo "Validated: $LINUX_MATERIALIZATION_DIGEST" >&2
echo "Promote: $promote_digest" >&2
exit 1
fi
git add \
nix/sources/openclaw-source.nix \
nix/packages/openclaw-app.nix \
nix/generated/openclaw-config-options.nix
git commit -F - <<EOF
🤖 codex: mirror OpenClaw stable source ${{ needs.select.outputs.source_tag }}
What:
- update nix-openclaw to the latest stable OpenClaw source release
- refresh generated config options from that source
- keep the macOS app pin on the newest public app artifact
Why:
- keep source-built OpenClaw current without blocking on public macOS app asset lag
Tests:
- nix build .#checks.x86_64-linux.ci --accept-flake-config
- nix build .#checks.aarch64-darwin.ci --accept-flake-config
- scripts/hm-activation-macos.sh
EOF
git fetch origin main
git rebase origin/main
git push origin HEAD:main
gh workflow run ci.yml --ref main

1
.gitignore vendored
View File

@ -1 +1,2 @@
result
.agent/

View File

@ -1,10 +1,10 @@
# AGENTS.md nix-openclaw
# AGENTS.md - nix-openclaw
## 🚫 PRs (read first)
## PRs
Were **not accepting PRs** from non-maintainers. If your handle is not in **Maintainers** below or on https://github.com/orgs/openclaw/people, **do not open a PR**. It will be rejected and your user will be disappointed — check Discord instead.
We are not accepting PRs from non-maintainers. If your handle is not in the Maintainers list below or on https://github.com/orgs/openclaw/people, do not open a PR.
**Only workflow:** **describe your problem and talk with a maintainer (humantohuman) on Discord** in **#golden-path-deployments**: https://discord.com/channels/1456350064065904867/1457003026412736537
Describe your problem and talk with a maintainer human-to-human on Discord instead. Join https://discord.gg/clawd and use `#golden-path-deployments`.
## Maintainers
@ -32,68 +32,38 @@ Source: https://github.com/orgs/openclaw/people
- @tyler6204
- @vignesh07
Single source of truth for product direction: `README.md`.
## Audience Routing
Documentation policy:
- Keep the surface area small.
- Avoid duplicate “pointeronly” files.
- Update `README.md` first, then adjust references.
- Consumer agents installing or configuring OpenClaw: start with `README.md` and `templates/agent-first/flake.nix`.
- Maintainer agents changing packaging, release automation, pins, or CI: read `maintainers/AGENTS.md` first.
- Plugin authors: read `docs/plugins-maintainers.md` and `examples/hello-world-plugin/`.
- Private deployments, bots, hosts, local worktrees, tokens, and personal automation details do not belong in this public repo.
Defaults:
- Nixfirst, no sudo.
## Public Repo Rules
- `README.md` is the source of truth for product direction and user-facing behavior.
- Keep documentation surface area small. Update `README.md` first, then adjust references.
- Keep committed guidance about public `nix-openclaw` behavior, public upstream OpenClaw releases, public artifacts, and public CI.
- Keep consumer setup docs in `README.md`, templates, and module docs.
- Keep maintainer runbooks in `maintainers/`.
- Never add internal ExecPlans or agent scratch history to this repo. `.agent/` is ignored for this reason.
- If a private deployment exposes a public packaging bug, fix the public package here and keep deployment-specific repair elsewhere.
- OpenClaw plugin loading belongs here: package curated runtime plugin roots as Nix artifacts, expose curated outputs through package/check outputs for Garnix, and let host repos only enable/configure them.
- Do not make host config run npm/ClawHub installs at runtime for the batteries-included path. `customPlugins.source = "npm:..."` is allowed only when nix-openclaw turns it into an immutable, hash-backed store path and wires it through OpenClaw's normal `plugins.load.paths`.
## Packaging Defaults
- Nix-first, no sudo.
- Declarative config only.
- Batteriesincluded install is the baseline.
- Breaking changes are acceptable pre1.0.0 (move fast, keep docs accurate).
- No deprecations; use breaking changes.
- NO INLINE SCRIPTS EVER.
- NEVER send any message (iMessage, email, SMS, etc.) without explicit user confirmation:
- Always show the full message text and ask: “Im going to send this: <message>. Send? (y/n)”
- Batteries-included install is the baseline.
- Breaking changes are acceptable pre-1.0.0; no deprecations.
- No inline scripts or inline file contents in Nix code. Use repo scripts and explicit file paths.
- The gateway package must include Control UI assets.
- User-facing docs should lead with one package: `openclaw`. Treat `openclaw-gateway` and `openclaw-app` as component outputs for modules, checks, and debugging.
- QMD is the Nix-supported batteries-included local memory backend. Keep `qmd` internal to the `openclaw` wrapper PATH; users opt in with upstream config.
OpenClaw packaging:
- The gateway package must include Control UI assets (run `pnpm ui:build` in the Nix build).
## Safety
Golden path for pins (yolo + manual bumps):
- Hourly GitHub Action **Yolo Update Pins** runs `scripts/update-pins.sh`, which:
- Picks latest upstream openclaw SHA with green non-Windows checks
- Rebuilds gateway to refresh `pnpmDepsHash`
- Regenerates `nix/generated/openclaw-config-options.nix` from upstream schema
- Updates app pin/hash, commits, rebases, pushes to `main`
- Manual bump (rare): `GH_TOKEN=... scripts/update-pins.sh` (same steps as above). Use only if yolo is blocked.
- To verify freshness: `git pull --ff-only` and check `nix/sources/openclaw-source.nix` vs `git ls-remote https://github.com/openclaw/openclaw.git refs/heads/main`.
- If upstream is moving fast and tighter freshness is needed, trigger yolo manually: `gh workflow run "Yolo Update Pins"`.
CI polling (hard rule):
- Never say "I'll keep polling" unless you are **already** running a blocking loop.
- If you must report status, confirm the loop is active (`tmux ls` / session name).
- Use a blocking bash loop in tmux (preferred) or a sub-agent; do not fake it.
- Example: `tmux new -s nix-openclaw-ci '/tmp/poll-nix-openclaw-ci.sh'`.
Philosophy:
The Zen of ~~Python~~ OpenClaw, ~~by~~ shamelessly stolen from Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Nix file policy:
- No inline file contents in Nix code, ever.
- Always reference explicit file paths (keep docs as real files in the repo).
- No inline scripts in Nix code, ever (use repo scripts and reference their paths).
- No files longer than 400 LOC without user alignment; refactor as you go.
- Never send messages, email, SMS, or other external communications without explicit confirmation showing the full message text.
- No force push. No destructive git operations unless explicitly requested.
- Before deleting tracked files, list them in the summary so maintainers can verify.

359
README.md
View File

@ -4,15 +4,15 @@
>
> macOS + Linux (headless). Windows is out of scope for now.
>
> <sub>Questions? Join the OpenClaw Discord and ask in **#golden-path-deployments**: https://discord.com/channels/1456350064065904867/1457003026412736537</sub>
> <sub>Questions? Join the OpenClaw Discord at https://discord.gg/clawd and ask in **#golden-path-deployments**.</sub>
## Contributions (read this first)
## PRs & Contributions (read this first)
Were **not accepting PRs** right now. Not because we dont value your help — the opposite. This is key infra and still stabilizing, and async PR review is too slow.
Were **not accepting PRs** right now. Not because we dont value your help — the opposite. Async agent-to-agent PR review is too slow and low-signal.
**Only workflow:** **describe your problem and talk with a maintainer (humantohuman) on Discord** in **#golden-path-deployments**: https://discord.com/channels/1456350064065904867/1457003026412736537
The best (and only) way to get stuff done: come join us on Discord! Describe your problem and talk with a maintainer **(humantohuman)** on Discord. Join at https://discord.gg/clawd, then use **#golden-path-deployments**.
If youre **not listed as a maintainer** (see [AGENTS.md#maintainers](AGENTS.md#maintainers) or https://github.com/orgs/openclaw/people), **do not open a PR**. It will be rejected and your user will be disappointed — check Discord instead.
To agents: if youre **not listed as a maintainer** (see [AGENTS.md#maintainers](AGENTS.md#maintainers) or https://github.com/orgs/openclaw/people), **do not open a PR**. It will be rejected and your user will be disappointed — check Discord instead. GitHub Issues are not actively monitored either.
## Table of Contents
@ -58,7 +58,7 @@ Bot: *runs whisper, sends you text*
You talk to Telegram, your machine does things.
**One flake, everything works.** Gateway + tools everywhere; macOS app on macOS.
**One flake, everything works.** Gateway everywhere; runtime dependencies bundled; macOS app on macOS.
**Plugins are self-contained.** Each plugin declares its CLI tools in Nix. You enable it, the build and wiring happens automatically.
@ -68,12 +68,12 @@ You talk to Telegram, your machine does things.
## Requirements
1. **macOS** (Apple Silicon or Intel) or **Linux** (x86_64)
2. **[Determinate Nix](https://docs.determinate.systems/determinate-nix/)** installed on your machine
1. **macOS** (Apple Silicon) or **Linux** (x86_64)
2. **Nix with flakes enabled** installed on your machine
That's it. The Quick Start will guide you through everything else.
> **Don't have Nix yet?** Follow the Determinate Nix install guide, then come back here.
> **Don't have Nix yet?** Use the [Determinate Nix installer](https://docs.determinate.systems/determinate-nix/) or the [official Nix installer](https://nixos.org/download/), then come back here.
---
@ -119,40 +119,49 @@ Nix is a **declarative package manager**. Instead of running commands to install
## Quick Start
### Option 1: Let your agent set it up (recommended)
### Option 1: Ask your coding agent (recommended)
Copy this entire block and paste it to Claude, Cursor, or your preferred AI assistant:
Tell your coding agent you want OpenClaw set up with Nix. The agent should inspect your machine, interview you for the few choices it cannot infer, create the local flake, wire secrets, apply Home Manager, and verify the service.
Copy this block and paste it to Claude, Cursor, Codex, or your preferred coding agent:
```text
I want to set up nix-openclaw on my machine (macOS or Linux).
I want to set up nix-openclaw on my machine (Apple Silicon macOS or x86_64 Linux).
Repository: github:openclaw/nix-openclaw
What nix-openclaw is:
- Batteries-included Nix package for OpenClaw (AI assistant gateway)
- Installs gateway + tools everywhere; macOS app only on macOS
- Installs the gateway everywhere; macOS app only on macOS
- Runs as a launchd service on macOS, systemd user service on Linux
What I need you to do:
1. Check if Determinate Nix is installed (if not, install it)
2. Create a local flake at ~/code/openclaw-local using templates/agent-first/flake.nix
3. Create a docs dir next to the config (e.g., ~/code/openclaw-local/documents) with AGENTS.md, SOUL.md, TOOLS.md (optional: IDENTITY.md, USER.md, LORE.md, HEARTBEAT.md, PROMPTING-EXAMPLES.md)
1. Inspect my OS, CPU architecture, shell, Home Manager setup, and whether Nix with flakes is installed
2. Ask me only for missing choices: channel, bot/account secrets, allowed users, provider keys, and documents/identity preferences
3. Create a local flake at ~/code/openclaw-local using templates/agent-first/flake.nix
4. Create a docs dir next to the config (e.g., ~/code/openclaw-local/documents) with AGENTS.md, SOUL.md, TOOLS.md (optional: IDENTITY.md, USER.md, LORE.md, HEARTBEAT.md, PROMPTING-EXAMPLES.md)
- If ~/.openclaw/workspace already has these files, adopt them into the documents dir first (use copy/rsync that dereferences symlinks, e.g. `cp -L`)
4. Help me create a Telegram bot (@BotFather) and get my chat ID (@userinfobot)
5. Set up secrets (bot token, Anthropic key) - plain files at ~/.secrets/ is fine
6. Fill in the template placeholders and run home-manager switch
7. Verify: service running, bot responds to messages
5. Help me create or connect the channel account I choose
6. Set up secrets (bot token, provider key) - plain files at ~/.secrets/ are fine unless I already have a secret manager
7. Ask whether I want local memory through QMD; if yes, set `memory.backend = "qmd"` in OpenClaw config
8. Fill in the template placeholders and run home-manager switch
9. Verify end-to-end: package builds, service is running, gateway health works, QMD works if enabled, and the bot/channel responds if configured
My setup:
- OS: [macOS / Linux]
- CPU: [arm64 / x86_64]
- System: [aarch64-darwin / x86_64-darwin / x86_64-linux]
- System: [aarch64-darwin / x86_64-linux]
- Home Manager config name: [FILL IN or "I don't have Home Manager yet"]
Reference the README and templates/agent-first/flake.nix in the repo for the module options.
```
Your agent will install Nix, create your config, and get OpenClaw running. You just answer its questions.
Your agent should do the setup work. You answer its short questions and confirm before it sends messages or changes external services.
QMD packaging note for agents: Linux uses upstream `github:tobi/qmd`; Darwin
uses the `nix-openclaw-tools` QMD repair package until upstream Darwin packaging
is fixed. Keep both pinned to the same QMD release unless there is a tested
reason to diverge.
**What happens next:**
1. Your agent sets everything up and runs `home-manager switch`
@ -165,16 +174,17 @@ Your agent will install Nix, create your config, and get OpenClaw running. You j
### macOS (Home Manager + launchd)
1. Install Determinate Nix.
1. Install Nix with flakes enabled.
2. Create a local config:
```bash
mkdir -p ~/code/openclaw-local && cd ~/code/openclaw-local
nix flake init -t github:openclaw/nix-openclaw#agent-first
```
3. Edit `flake.nix` placeholders:
- `system` = `aarch64-darwin` (Apple Silicon) or `x86_64-darwin` (Intel)
- `system` = `aarch64-darwin`
- `home.username` and `home.homeDirectory`
- `programs.openclaw.documents` with `AGENTS.md`, `SOUL.md`, `TOOLS.md` (optional: `IDENTITY.md`, `USER.md`, `LORE.md`, `HEARTBEAT.md`, `PROMPTING-EXAMPLES.md`)
- Keep this directory inside the flake, or make sure the Nix daemon can read it and traverse every parent directory.
- Provider secrets (Telegram/Discord tokens, Anthropic API key)
4. Apply:
```bash
@ -187,7 +197,7 @@ Your agent will install Nix, create your config, and get OpenClaw running. You j
### Linux (headless + systemd user service)
1. Install Determinate Nix.
1. Install Nix with flakes enabled.
2. Create a local config:
```bash
mkdir -p ~/code/openclaw-local && cd ~/code/openclaw-local
@ -197,6 +207,7 @@ Your agent will install Nix, create your config, and get OpenClaw running. You j
- `system` = `x86_64-linux`
- `home.username` and `home.homeDirectory` (e.g., `/home/<user>`)
- `programs.openclaw.documents` with `AGENTS.md`, `SOUL.md`, `TOOLS.md` (optional: `IDENTITY.md`, `USER.md`, `LORE.md`, `HEARTBEAT.md`, `PROMPTING-EXAMPLES.md`)
- Keep this directory inside the flake, or make sure the Nix daemon can read it and traverse every parent directory.
- Provider secrets (Telegram/Discord tokens, Anthropic API key)
4. Apply:
```bash
@ -254,21 +265,22 @@ All state lives in `~/.openclaw/`. Logs at `/tmp/openclaw/openclaw-gateway.log`.
Plugins extend what OpenClaw can do. Each plugin bundles tools and teaches the AI how to use them.
### First-party plugins
### Bundled plugins
These ship with nix-openclaw. Toggle them in your config:
These ship with nix-openclaw. Catalog source of truth: `nix/modules/home-manager/openclaw/plugin-catalog.nix`.
Toggle them in your config:
```nix
programs.openclaw.bundledPlugins = {
summarize.enable = true; # Summarize web pages, PDFs, videos
discrawl.enable = false; # Discord archive/search
wacrawl.enable = false; # WhatsApp archive/search
peekaboo.enable = true; # Take screenshots
oracle.enable = false; # Web search
poltergeist.enable = false; # Control your macOS UI
poltergeist.enable = false; # File watching and automation
sag.enable = false; # Text-to-speech
camsnap.enable = false; # Camera snapshots
gogcli.enable = false; # Google Calendar
goplaces.enable = true; # Google Places API
bird.enable = false; # Twitter/X
sonoscli.enable = false; # Sonos control
imsg.enable = false; # iMessage
};
@ -283,31 +295,48 @@ programs.openclaw.bundledPlugins.goplaces = {
| Plugin | What it does |
|--------|--------------|
| `summarize` | Summarize URLs, PDFs, YouTube videos |
| `discrawl` | Archive and search Discord history |
| `wacrawl` | Archive and search WhatsApp Desktop history |
| `peekaboo` | Screenshot your screen |
| `oracle` | Search the web |
| `poltergeist` | Click, type, control macOS UI |
| `poltergeist` | File watching and automation |
| `sag` | Text-to-speech |
| `camsnap` | Take photos from connected cameras |
| `gogcli` | Google Calendar integration |
| `goplaces` | Google Places API (New) CLI |
| `bird` | Twitter/X integration |
| `sonoscli` | Control Sonos speakers |
| `imsg` | Send/read iMessages |
### Adding community plugins
Tell your agent: *"Add the plugin from github:owner/repo-name"*
Tell your agent: *"Add the plugin from github:owner/repo-name and pin it."*
Or add it manually to your config:
```nix
customPlugins = [
{ source = "github:owner/repo-name"; }
{ source = "github:owner/repo-name?rev=<commit>&narHash=<narHash>"; }
];
```
Then run `home-manager switch` to install.
For an OpenClaw native plugin published to npm, keep the source shape close to
OpenClaw's own install command and let Nix build the immutable plugin root:
```nix
customPlugins = [
{
source = "npm:@scope/openclaw-plugin@1.2.3";
id = "openclaw-plugin";
hash = lib.fakeHash; # replace with the sha256 Nix reports
}
];
```
Use this for OpenClaw runtime plugins with `openclaw.plugin.json` /
`package.json.openclaw`. It does not run npm at gateway startup; Nix builds and
caches the plugin root, then adds it to OpenClaw's `plugins.load.paths`.
### Plugins with configuration
Some plugins need settings (auth files, preferences). Here's a simplified example:
@ -316,7 +345,7 @@ Some plugins need settings (auth files, preferences). Here's a simplified exampl
# Example: a padel court booking plugin (simplified for illustration)
customPlugins = [
{
source = "github:example/padel-cli";
source = "github:example/padel-cli?rev=<commit>&narHash=<narHash>";
config = {
env = {
PADEL_AUTH_FILE = "~/.secrets/padel-auth"; # where your login token lives
@ -393,7 +422,7 @@ Contract to implement:
1) Add openclawPlugin output in flake.nix:
- name
- skills (paths to SKILL.md dirs)
- packages (CLI packages to put on PATH)
- packages (CLI packages to put on the OpenClaw runtime PATH)
- needs (stateDirs + requiredEnv)
Example:
@ -420,9 +449,9 @@ openclawPlugin = {
Standard plugin config shape (Nix-native, no JSON strings):
plugins = [
customPlugins = [
{
source = "github:owner/my-plugin";
source = "github:owner/my-plugin?rev=<commit>&narHash=<narHash>";
config = {
env = {
MYPLUGIN_AUTH_FILE = "/run/agenix/myplugin-auth";
@ -444,7 +473,7 @@ Config flags the host will use:
- `config.settings` for typed config keys (rendered to config.json in the first stateDir)
CI note:
- Make sure the plugin build is covered by CI (e.g. included in the repo's Nix build target list / cache-warming workflow).
- If the repo uses Garnix, add the plugin build to its `garnix.yaml` (or equivalent) so CI verifies it.
Why: explicit, minimal, fail-fast, no inline JSON strings.
Deliverables: flake output, env overrides, AGENTS.md, skill update.
@ -491,10 +520,7 @@ The simplest setup:
};
};
# Built-ins (tools + skills) shipped via nix-steipete-tools.
plugins = [
{ source = "github:openclaw/nix-steipete-tools?dir=tools/summarize"; }
];
bundledPlugins.summarize.enable = true;
};
}
```
@ -532,40 +558,37 @@ Uses `instances.default` to unlock per-group mention rules. If `instances` is se
};
};
bundledPlugins.peekaboo.enable = true;
customPlugins = [
{ source = "github:joshp123/xuezh?rev=<commit>&narHash=<narHash>"; }
{
source = "github:joshp123/padel-cli?rev=<commit>&narHash=<narHash>";
config = {
env = { PADEL_AUTH_FILE = "/run/agenix/padel-auth"; };
settings = {
default_location = "CITY_NAME";
preferred_times = [ "18:00" "20:00" ];
preferred_duration = 90;
venues = [
{
id = "VENUE_ID";
alias = "VENUE_ALIAS";
name = "VENUE_NAME";
indoor = true;
timezone = "TIMEZONE";
}
];
};
};
}
];
instances.default = {
enable = true;
package = pkgs.openclaw; # batteries-included
stateDir = "~/.openclaw";
workspaceDir = "~/.openclaw/workspace";
launchd.enable = true;
# Plugins (prod: pinned GitHub). Built-ins are via nix-steipete-tools.
# MVP target: repo pointers resolve to tools + skills automatically.
plugins = [
{ source = "github:openclaw/nix-steipete-tools?dir=tools/oracle"; }
{ source = "github:openclaw/nix-steipete-tools?dir=tools/peekaboo"; }
{ source = "github:joshp123/xuezh"; }
{
source = "github:joshp123/padel-cli";
config = {
env = { PADEL_AUTH_FILE = "/run/agenix/padel-auth"; };
settings = {
default_location = "CITY_NAME";
preferred_times = [ "18:00" "20:00" ];
preferred_duration = 90;
venues = [
{
id = "VENUE_ID";
alias = "VENUE_ALIAS";
name = "VENUE_NAME";
indoor = true;
timezone = "TIMEZONE";
}
];
};
};
}
];
};
};
}
@ -577,138 +600,88 @@ Uses `instances.default` to unlock per-group mention rules. If `instances` is se
### Dual-instance setup (prod + dev)
Use a shared base config and override only what's different. After changing local plugin or gateway code, re-run `home-manager switch` to rebuild.
Use named instances when you need two local gateways. Keep the default package unless you are actively debugging a local gateway checkout.
```nix
# flake inputs (pin prod + app)
inputs = {
nix-openclaw.url = "github:openclaw/nix-openclaw?ref=v0.1.0"; # pins macOS app + gateway bundle
};
programs.openclaw = {
documents = ./documents;
let
prodConfig = {
channels.telegram = {
tokenFile = "/run/agenix/telegram-prod";
allowFrom = [ 12345678 ];
instances = {
prod = {
enable = true;
gatewayPort = 18789;
config.channels.telegram = {
tokenFile = "/run/agenix/telegram-prod";
allowFrom = [ 12345678 ];
};
plugins = [
{ source = "github:owner/your-plugin?rev=<commit>&narHash=<narHash>"; }
];
};
};
devConfig = {
channels.telegram = {
tokenFile = "/run/agenix/telegram-dev";
allowFrom = [ 12345678 ];
};
};
prod = {
enable = true;
# Prod gateway pin (comes from nix-openclaw input @ v0.1.0 above).
package = inputs.nix-openclaw.packages.${pkgs.system}.openclaw-gateway;
config = prodConfig;
plugins = [ { source = "github:owner/your-plugin"; } ];
};
in {
# Pinned macOS app (POC: no local app builds, uses nix-openclaw @ v0.1.0 above).
programs.openclaw.appPackage =
inputs.nix-openclaw.packages.${pkgs.system}.openclaw-app;
programs.openclaw.documents = ./documents;
programs.openclaw.instances = {
prod = prod;
dev = prod // {
# Dev uses the same pinned macOS app (from nix-openclaw input),
# but overrides the gateway package to a local checkout.
config = devConfig;
dev = {
enable = true;
gatewayPort = 18790;
# Local gateway checkout (path). App stays pinned.
gatewayPath = "/Users/you/code/openclaw";
# Local plugin overrides prod if names collide (last wins).
plugins = prod.plugins ++ [
config.channels.telegram = {
tokenFile = "/run/agenix/telegram-dev";
allowFrom = [ 12345678 ];
};
plugins = [
{ source = "path:/Users/you/code/your-plugin"; }
{
source = "github:joshp123/padel-cli";
config = {
env = { PADEL_AUTH_FILE = "/run/agenix/padel-auth-dev"; };
settings = {
default_location = "CITY_NAME";
preferred_times = [ "18:00" ];
preferred_duration = 90;
venues = [];
};
};
}
];
};
};
}
};
```
### Plugin collisions
Plugins are keyed by their declared `name`. If two plugins declare the same name, the **last entry wins** (use this to override a prod plugin with a local dev one).
### Tool overrides (avoid collisions)
Home Manager auto-excludes `git` when `programs.git.enable = true`.
Drop built-in tools that you already install elsewhere:
```nix
programs.openclaw.excludeTools = [ "git" "jq" "ripgrep" ];
```
Or provide a custom list:
```nix
programs.openclaw.toolNames = [ "nodejs_22" "pnpm_10" "summarize" ];
```
If you override `programs.openclaw.package`, use `pkgs.openclawPackages.withTools { ... }.openclaw` to apply these lists.
---
## Packaging & Updates
**Goal:** `nix-openclaw` is a great Nix package. Automation, promotion, and fleet rollout live elsewhere.
### Stable only (for now)
### Stable release mirroring
We ship a single pinned upstream commit:
- **Stable**: last known-good pin. This is the default.
We ship one default package: `.#openclaw`.
The gateway tracks the newest upstream stable OpenClaw source release that satisfies the Nix package contract:
- gateway builds on Linux and macOS
- gateway starts and answers local health checks
The macOS app is pinned separately to the newest stable public `OpenClaw-*.zip` artifact. If upstream has not promoted desktop assets for the latest source release yet, `openclaw-app` may lag; that must not block Linux users or macOS gateway users from getting the latest source-built OpenClaw.
The Nix gate is deliberately package-focused. It does not make the full upstream Vitest suite a hard promotion gate; upstream owns source test health, while `nix-openclaw` verifies the source build, generated config options, package contents, smoke startup, module activation, and newest available macOS app artifact.
Outputs:
```
.#openclaw
.#openclaw-gateway
.#openclaw-app # Darwin only
```
Pin lives in:
`.#openclaw-gateway` and `.#openclaw-app` are component outputs for modules, CI, debugging, and advanced use. Start with `.#openclaw`.
Pins live in:
- `nix/sources/openclaw-source.nix`
- `nix/packages/openclaw-app.nix`
### Responsibilities (who owns what)
- **openclaw (upstream)**: source code, tests, releases.
- **nix-openclaw**: Nix packaging, pins, CI builds.
- **moltinators**: update cadence, smoke tests, promotion, rollout/rollback.
- **release automation**: update cadence, smoke tests, promotion, rollout/rollback.
### Automated pipeline (no manual steps)
### Automated pipeline
1) **moltinators updater** proposes a new stable pin.
2) **GitHub Actions** builds the package on Linux + macOS and runs gateway tests on Linux.
Build outputs are pushed to a binary cache (Cachix) so installs are fast and reproducible.
It also validates the generated Nix config options against the upstream schema.
3) **moltinators smoke test** runs against real Discord in `#moltinators-test`.
4) If green → promote to stable.
5) If red → keep current stable pin.
---
### CI & binary cache
This repo uses GitHub Actions for CI. For fast installs, we push build outputs to a **Cachix** binary cache.
Maintainers: configure these in GitHub repo settings (Actions):
- Variable: `CACHIX_CACHE_NAME`
- Secret: `CACHIX_AUTH_TOKEN`
Users: run `cachix use <cache-name>` (or configure `substituters` + public key manually) to get cache hits.
1) Hourly **Yolo Update Pins** polls upstream stable OpenClaw releases.
2) It selects the newest stable source release and newest stable public macOS app zip independently.
3) Newer source releases that lack public macOS app assets are reported as app lag, not skipped.
4) Yolo materializes the source pin from the newest source tag ref, updates the app asset pin from the newest public app zip, and regenerates config options from the selected source.
5) Yolo validates that source/app pin set on the same Linux + macOS contract as repository `CI`.
6) Only after both validations pass does yolo push one release-mirroring commit to `main`.
---
@ -744,10 +717,36 @@ home-manager switch --rollback # revert
| Package | Contents |
| --- | --- |
| `openclaw` (default) | macOS: gateway + app + tools · Linux: gateway + tools (headless) |
| `openclaw-gateway` | Gateway CLI only |
| `openclaw-tools` | Toolchain bundle (gateway helpers + CLIs) |
| `openclaw-app` | macOS app only |
| `openclaw` (default) | Canonical package. Exposes `openclaw`; keeps runtime tools internal. macOS also links the app. |
| `openclaw-gateway` | Component output: gateway CLI/service only |
| `openclaw-app` | Component output: macOS app only |
### Local memory
`openclaw` includes QMD internally as the supported local memory backend. It is not enabled automatically. Linux uses upstream `tobi/qmd`; Darwin uses the repaired `nix-openclaw-tools` package until upstream QMD is fixed there.
Opt in through normal OpenClaw config:
```nix
programs.openclaw.config = {
memory.backend = "qmd";
};
```
QMD stays inside the `openclaw` wrapper PATH, so users do not need to install a separate `qmd` command. The builtin `memorySearch.provider = "local"` path is an escape hatch for people who want to manage `node-llama-cpp` themselves; it is not the primary Nix-supported path.
Plugin CLIs are also kept on the OpenClaw runtime PATH by default, not on the user's login shell PATH. Set `programs.openclaw.exposePluginPackages = true` only when you explicitly want plugin CLIs in `home.packages`.
Optional model prewarming is also declarative:
```nix
programs.openclaw.qmd.prewarmModels.enable = true;
```
That runs a temporary QMD collection through `qmd update`, `qmd embed`, and
`qmd query` during Home Manager activation, which warms the default embedding,
expansion, and reranking models in the user's QMD cache. Expect about 2.25GB of
cache use.
### What we manage vs what you manage
@ -756,26 +755,24 @@ home-manager switch --rollback # revert
| Gateway binary | ✓ | |
| macOS app | ✓ | |
| Service (launchd/systemd) | ✓ | |
| Tools (whisper, etc) | ✓ | |
| Runtime tools and QMD | ✓ | |
| Telegram bot token | | ✓ |
| Anthropic API key | | ✓ |
| Chat IDs | | ✓ |
### Included tools
### Runtime tools
> **Platform note:** the toolchain is filtered per platform. macOS-only tools are skipped on Linux.
**Core**: nodejs, pnpm, git, curl, jq, python3, ffmpeg, ripgrep
The default `openclaw` package uses these tools internally and does not expose them as separate user commands.
**Firstparty tools** are sourced from `nix-steipete-tools` when available (currently aarch64darwin).
**Core**: nodejs, pnpm, git, curl, jq, python3, ffmpeg, sox, ripgrep
**AI/ML**: openai-whisper, sag (TTS)
**Local memory**: QMD (`memory.backend = "qmd"` opt-in)
**Media**: spotify-player, sox, camsnap
**Default first-party tools** come from `nix-openclaw-tools`: gogcli (`gog`), goplaces, summarize, camsnap, sonoscli.
**macOS**: peekaboo, blucli
**Integrations**: gogcli, goplaces, wacli, bird, mcporter
**Optional bundled plugins** add their own packages when enabled: discrawl, wacrawl, peekaboo, poltergeist, sag, imsg.
---

View File

@ -1,5 +0,0 @@
# AgentFirst Guide
Single source of instructions: `templates/agent-first/steps.md`.
This guide intentionally contains **no code blocks**. All code lives in files under `templates/agent-first/`.

View File

@ -1,55 +0,0 @@
# CI cache migration plan (Garnix → Cachix) — paused
Status: **paused**. Branch captures WIP so `main` can return to Garnix while we re-evaluate.
## Goal
- Get back to **reliable cached builds** for nix-openclaw.
- Avoid CI hard-stops like the Garnix “monthly CI quota exhausted” failure.
## Why we started this
- Garnix checks were failing with: `You have exhausted your monthly CI quota`.
- That prevents building → prevents cache population → makes installs slow.
## What we changed in this branch (WIP)
- Added GitHub Actions workflow(s) intended to:
- build the same target set as `garnix.yaml` (Linux + macOS)
- push build outputs to a Cachix binary cache
- Reworked/removed Garnix-specific cache verification.
- Removed `garnix.yaml` (later reverted plan: keep it on main while we test Garnix recovery).
## Key missing piece / blocker
Cachix has two separate capabilities:
1) **Cache management** (create cache, toggle visibility, keys, members)
2) **Data plane** (upload NARs / store paths)
The token we generated appears to be **data-plane only** (JWT scope looked like `tx`).
- It cant authenticate to Cachix management endpoints (`/api/v1/organization`, `/api/v1/token`, etc.), which are required to create/own a cache.
- Result: we cant fully “self-serve” cache provisioning via API with that token alone.
## If we resume: recommended path
### Option A (simple)
- Create a Cachix cache manually in the Cachix UI (name: `nix-openclaw`, public).
- Then CI only needs:
- `CACHIX_CACHE_NAME=nix-openclaw`
- `CACHIX_AUTH_TOKEN=<push token>`
### Option B (fully automated)
- Obtain a Cachix token with **management** permissions (org/account) that can:
- read org/account (`GET /api/v1/organization`)
- create cache (`POST /api/v1/cache/{name}`)
- set visibility / generate signing key
## Proposed CI shape (once unblocked)
- Workflow: `Nix Build Cache`
- matrix-ish: Linux + macOS
- run `nix build` for target list
- push store paths to Cachix (`cachix watch-exec ...`)
- (Optional) Workflow: `Cache Only`
- verify required outputs exist in cache (nice-to-have guard)
## Rollback / safety
- Keep Garnix config on `main` until Cachix is proven.
- Only then remove Garnix checks/config.
## Notes
- Any additional secrets should be stored in `nix-secrets` and synced into GitHub Actions secrets.

View File

@ -7,6 +7,24 @@ Purpose: extend OpenClaw capabilities without bloating core; ship tools + skills
- **Not:** new transports/providers; model plumbing; secrets baked in; inline scripts or ad-hoc package-manager installs; a place for random config outside its scope.
- Why not skills-only: skills without binaries can hallucinate capability. Plugins ground skills in real tools and deliver versioned, reproducible functionality.
## Two Plugin Classes
Nix capability plugins are the tool/skill/env bundles described below. They do not use OpenClaw's JavaScript plugin loader. They are the right shape for CLIs such as `goplaces`, `gog`, `qmd`, `xuezh`, `camsnap`, and `summarize`.
OpenClaw plugins are runtime plugin directories with `openclaw.plugin.json` plus built JavaScript loaded by the gateway. They include bundled upstream plugins, official external plugins from OpenClaw's catalog or ClawHub, and third-party plugins. In Nix-managed deployments, these should be immutable plugin roots, not runtime npm installs hidden in host config.
Current nix-openclaw `customPlugins` implements both sides of the contract: package binaries on the gateway PATH, materialize skills, create state dirs, validate env files, render optional tool settings, and wire declared OpenClaw plugin roots into `plugins.load.paths` with an explicit default `plugins.entries.<id>.enabled` value.
PR #81 (`fix: copy plugin manifests into dist/extensions`) was related but not the missing external-plugin feature. It fixed bundled upstream plugin manifests missing from the packaged gateway `dist/extensions/*/openclaw.plugin.json` tree. Current packaging already copies those manifests and checks them in `openclaw-package-contents`.
Package authors can bridge the existing Nix contract to OpenClaw plugins:
- Extend `openclawPlugin` with an optional plugin declaration, for example `plugins = [ { id = "openclaw-weixin"; path = "${pkg}/lib/openclaw/plugins/openclaw-weixin"; enabled = true; } ];`.
- For each selected plugin artifact, append those paths to generated `plugins.load.paths`.
- Add a default `plugins.entries.<id>.enabled` value. `enabled` defaults to true, but plugin authors can set `enabled = false` for roots that should be discoverable while disabled until the host supplies config. User config can still override either default.
- Keep OpenClaw plugin config in `programs.openclaw.config` / `instances.<name>.config` so upstream schema validation remains the source of truth.
- Add a fixture shaped like `openclaw-weixin` so `customPlugins = [{ source = ...; }]` proves both package/skill wiring and OpenClaw plugin load wiring.
## Interface Contract (reference implementation: nix-openclaw)
Every plugin artifact exposes the same fields (flake output `openclawPlugin` today, but the shape is host-agnostic):
@ -14,7 +32,8 @@ Every plugin artifact exposes the same fields (flake output `openclawPlugin` tod
openclawPlugin = {
name = "summarize"; # unique; last-wins on collision
skills = [ ./skills/summarize ]; # dirs containing SKILL.md
packages = [ pkgs.summarize-cli ]; # binaries placed on PATH
packages = [ pkgs.summarize-cli ]; # binaries placed on the OpenClaw runtime PATH
plugins = [ ]; # optional OpenClaw plugin roots: { id, path, enabled ? true }
needs = {
stateDirs = [ ".config/summarize" ]; # created under $HOME
requiredEnv = [ "SUMMARIZE_API_KEY" ]; # must point to files
@ -27,18 +46,19 @@ Host responsibilities (what the runtime guarantees):
- Install `packages`; prepend to PATH for the gateway wrapper.
- Create `needs.stateDirs` under `$HOME`.
- Fail fast if any `requiredEnv` is unset or points to a missing/empty file.
- Copy/symlink `skills` into `workspace/skills/<name>/...`.
- Copy/symlink each `skills` entry into `workspace/skills/<skill-dir-basename>/...`.
- If host config provides `config.settings`, render it to `config.json` in the first `stateDir`.
- Export `config.env` (plus required envs) into the gateway wrapper.
- Add declared OpenClaw plugin roots to `plugins.load.paths`, and set `plugins.entries.<id>.enabled` from the plugin contract as a default.
- Reject duplicate skill paths; duplicate plugin names: last entry wins.
### Host-side config shape
When enabling a plugin, the host can supply:
```nix
plugins = [
programs.openclaw.customPlugins = [
{
source = "github:owner/repo";
source = "github:owner/repo?rev=<commit>&narHash=<narHash>";
config = {
env = { KEY = "/run/agenix/key"; EXTRA = "/path/to/file"; };
settings = { foo = "bar"; retries = 3; };
@ -51,23 +71,43 @@ plugins = [
- `config.settings`: JSON-rendered into `config.json` inside the first `stateDir`.
- Invariant: providing `settings` requires at least one `stateDir`.
Do not add raw npm package names to host config for the batteries-included path. Curated plugins packaged by this repo or `nix-openclaw-tools` should be exposed through package/check outputs so Garnix caches them.
OpenClaw native npm plugins use the same host list with an OpenClaw-style source:
```nix
programs.openclaw.customPlugins = [
{
source = "npm:@scope/openclaw-plugin@1.2.3";
id = "openclaw-plugin";
hash = lib.fakeHash; # replace with the sha256 Nix reports
}
];
```
- `source`: currently supports registry npm specs with an explicit `npm:` prefix.
- `id`: required because the Home Manager module must enable the plugin at eval time without importing the built JavaScript package.
- `hash`: recursive output hash for the immutable plugin root; leave as `lib.fakeHash` to have Nix report the expected hash, then commit that value.
- Runtime plugin config belongs in `programs.openclaw.config.plugins.entries.<id>.config`, not in `customPlugins.config`.
- The module adds the built root to `plugins.load.paths` and writes a default `plugins.entries.<id>.enabled` value. OpenClaw owns runtime loading after that.
Curated npm plugins can be added to this repo or `nix-openclaw-tools` so Garnix caches them. Arbitrary user npm specs are still deterministic Nix artifacts, but this repo's cache cannot cover every user's private plugin choice. The user's local store or configured binary cache reuses the artifact until the source or hash changes. OpenClaw must not reinstall it on every gateway start.
## Dev workflow (fast iteration)
- Worktree: build and test plugins outside the core repo; point OpenClaw at a local path source (e.g., `source = "path:/Users/you/code/my-plugin"`).
- Worktree: build and test plugins outside the core repo; point OpenClaw at a local path source during impure local dev (e.g., `source = "path:/Users/you/code/my-plugin"`). Committed config uses pinned refs.
- Rebuild loop: change plugin → `home-manager switch` (or host-equivalent) → gateway restarts with new PATH/skills/config; no manual copying.
- Name collisions: use the same plugin `name` to override a pinned version (last entry wins); keep unique names otherwise to avoid surprise overrides.
- Skills placement: skills land under `~/.openclaw*/workspace/skills/<plugin>/...` so you can inspect quickly; delete the workspace to fully reset cached skills.
- Skills placement: skills land under `~/.openclaw*/workspace/skills/<skill-dir-basename>/...` so you can inspect quickly; delete the workspace to fully reset cached skills.
- Env guardrails: required env vars must point to files (non-empty) or the activation fails—supply temp files during dev to exercise the checks.
- Settings JSON: inspect the rendered `config.json` in the first `stateDir` to confirm schema and defaults before committing.
## Examples
### Minimal capability plugin (first-party `summarize`)
### Minimal capability plugin (bundled `summarize`)
Enable (host side):
```nix
programs.openclaw.instances.default.plugins = [
{ source = "github:openclaw/nix-steipete-tools?dir=tools/summarize"; }
];
programs.openclaw.bundledPlugins.summarize.enable = true;
```
Plugin contract (inside the plugin repo):
@ -85,9 +125,9 @@ openclawPlugin = {
Enable (host side):
```nix
programs.openclaw.instances.default.plugins = [
programs.openclaw.customPlugins = [
{
source = "github:joshp123/xuezh";
source = "github:joshp123/xuezh?rev=<commit>&narHash=<narHash>";
config = {
env = {
# Required envs (guarded as files):
@ -131,8 +171,9 @@ openclawPlugin = {
Host behavior: creates `~/.config/xuezh/config.json` from `settings`; exports both envs; fails if the pointed files are missing/empty.
## Bundled Plugin Set (current)
- summarize, peekaboo, oracle, poltergeist, sag, camsnap, gogcli, goplaces, bird, sonoscli, imsg.
- Each follows the same contract: packages + skills; env/state declared via `needs`; enabled via config toggle; sources pinned (see nix-openclaw bundledPlugins mapping).
- summarize, discrawl, wacrawl, peekaboo, poltergeist, sag, camsnap, gogcli, goplaces, sonoscli, imsg.
- Source of truth: `nix/modules/home-manager/openclaw/plugin-catalog.nix`.
- Each follows the same contract: packages + skills; env/state declared via `needs`; enabled via config toggle; sources pinned via the bundled plugin catalog.
## Authoring Rules
- Keep CLIs configurable via env; honor XDG paths; no inline scripts.

View File

@ -26,7 +26,7 @@ This RFC is only about:
- A generic, enduser Nix setup that lives outside any personal config repo.
This RFC is explicitly **not** about:
- Joshs personal `nixos-config` or any private machine configuration.
- Any personal system configuration repo or private machine configuration.
- Editing or publishing personal settings, tokens, or machinespecific modules.
## 2) Goals / Nongoals

View File

@ -50,7 +50,7 @@ plugin/
That's it. No registry. No central authority. Point at a repo, get a plugin.
One install gives you:
- **Binary** on PATH (built from source, pinned version)
- **Binary** on the OpenClaw runtime PATH (built from source, pinned version)
- **Skills** in workspace (agent knows how to use it)
- **Config** validated (missing env = install fails, not runtime error)
- **State dirs** created (plugin has a home)
@ -203,7 +203,7 @@ voicecall status --call-id abc123 # Check for responses
4. **Create state dirs** — from manifest
5. **Add `openclaw plugins` CLI** — list, enable, disable, info
That's it. No dynamic code loading, no TypeBox registration, no RPC handlers. Just: find plugins, validate their needs, put binaries on PATH, copy skills to workspace.
That's it. No dynamic code loading, no TypeBox registration, no RPC handlers. Just: find plugins, validate their needs, put binaries on the OpenClaw runtime PATH, copy skills to workspace.
### How nix-openclaw fits in
@ -217,7 +217,7 @@ programs.openclaw.customPlugins = [
{ source = "github:joshp123/padel-cli"; }
# Local dev: point at directory
{ source = "path:/Users/josh/code/my-plugin"; }
{ source = "path:/home/user/code/my-plugin"; }
];
# Or enable bundled plugins (pinned in nix-openclaw):
@ -304,17 +304,18 @@ Install wires up Twilio creds. Binary handles webhook server. Skill teaches agen
## The Plugin Ecosystem Vision
**First-party plugins** already exist — see [nix-steipete-tools](https://github.com/openclaw/nix-steipete-tools/tree/main/tools):
**First-party plugins** already exist — see [nix-openclaw-tools](https://github.com/openclaw/nix-openclaw-tools/tree/main/tools):
- `summarize` — YouTube/article summarization
- `oracle` — second-model review
- `discrawl` — Discord archive/search
- `wacrawl` — WhatsApp archive/search
- `peekaboo` — screenshot capture
- `camsnap` — webcam capture
- `poltergeist` — browser automation
- `sag` — web search
- `bird` — Twitter/X integration
- `poltergeist` — file watching and automation
- `sag` — text-to-speech
- `sonoscli` — Sonos control
- `imsg` — iMessage integration
- `gogcli` — Google Calendar
- `goplaces` — Google Places
All follow the same contract. All pinned in nix-openclaw. Enable with one line:
```nix

View File

@ -6,11 +6,18 @@
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
in {
in
{
packages.default = pkgs.buildGoModule {
pname = "hello-world";
version = "0.1.0";
@ -27,8 +34,8 @@
skills = [ ./skills/hello-world ];
packages = [ self.packages.${system}.default ];
needs = {
stateDirs = [];
requiredEnv = [];
stateDirs = [ ];
requiredEnv = [ ];
};
};
}

41
flake.lock generated
View File

@ -38,21 +38,21 @@
"type": "github"
}
},
"nix-steipete-tools": {
"nix-openclaw-tools": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1770240566,
"narHash": "sha256-fY8t41kMSHu2ovf89mIdvC7vkceroCwKxw/MKVn4rsE=",
"lastModified": 1778060041,
"narHash": "sha256-tXWkN1VnwFG8XlRqW/e7VwbKnUfyU9tB7YDm9QHJXTY=",
"owner": "openclaw",
"repo": "nix-steipete-tools",
"rev": "983210e3b6e9285780e87f48ce9354b51a270e95",
"repo": "nix-openclaw-tools",
"rev": "4c1cee3c7eaf68f9de0f756be1484534f5bb5f34",
"type": "github"
},
"original": {
"owner": "openclaw",
"repo": "nix-steipete-tools",
"repo": "nix-openclaw-tools",
"type": "github"
}
},
@ -88,12 +88,37 @@
"type": "github"
}
},
"qmd": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1775429264,
"narHash": "sha256-bqIVaNRTa8H5vrw3RwsD7QdtTa0xNvRuEVzlzE1hIBQ=",
"owner": "tobi",
"repo": "qmd",
"rev": "65cd1b3fd02891d1ee0eefa751620918664fa321",
"type": "github"
},
"original": {
"owner": "tobi",
"ref": "v2.1.0",
"repo": "qmd",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"home-manager": "home-manager",
"nix-steipete-tools": "nix-steipete-tools",
"nixpkgs": "nixpkgs_2"
"nix-openclaw-tools": "nix-openclaw-tools",
"nixpkgs": "nixpkgs_2",
"qmd": "qmd"
}
},
"systems": {

156
flake.nix
View File

@ -13,70 +13,164 @@
flake-utils.url = "github:numtide/flake-utils";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
nix-steipete-tools.url = "github:openclaw/nix-steipete-tools";
nix-openclaw-tools.url = "github:openclaw/nix-openclaw-tools";
qmd.url = "github:tobi/qmd/v2.1.0";
qmd.inputs.flake-utils.follows = "flake-utils";
qmd.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, flake-utils, home-manager, nix-steipete-tools }:
outputs =
{
self,
nixpkgs,
flake-utils,
home-manager,
nix-openclaw-tools,
qmd,
}:
let
overlay = import ./nix/overlay.nix;
openclawToolPkgsFor =
system:
if nix-openclaw-tools ? packages && builtins.hasAttr system nix-openclaw-tools.packages then
nix-openclaw-tools.packages.${system}
else
{ };
qmdPkgsFor =
system:
if qmd ? packages && builtins.hasAttr system qmd.packages then qmd.packages.${system} else { };
overlay =
final: prev:
import ./nix/overlay.nix {
openclawToolPkgs = openclawToolPkgsFor prev.stdenv.hostPlatform.system;
qmdPkgs = qmdPkgsFor prev.stdenv.hostPlatform.system;
} final prev;
sourceInfoStable = import ./nix/sources/openclaw-source.nix;
systems = [ "x86_64-linux" "aarch64-darwin" ];
sourceInfoDogfood = import ./nix/sources/openclaw-dogfood-source.nix;
systems = [
"x86_64-linux"
"aarch64-darwin"
];
in
flake-utils.lib.eachSystem systems (system:
flake-utils.lib.eachSystem systems (
system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [ overlay ];
};
steipetePkgs = if nix-steipete-tools ? packages && builtins.hasAttr system nix-steipete-tools.packages
then nix-steipete-tools.packages.${system}
else {};
openclawToolPkgs = openclawToolPkgsFor system;
qmdPkgs = qmdPkgsFor system;
qmdPackage =
if pkgs.stdenv.hostPlatform.isDarwin then
openclawToolPkgs.qmd or null
else
qmdPkgs.qmd or qmdPkgs.default or null;
packageSetStable = import ./nix/packages {
pkgs = pkgs;
sourceInfo = sourceInfoStable;
steipetePkgs = steipetePkgs;
openclawToolPkgs = openclawToolPkgs;
inherit qmdPackage;
};
packageSetDogfood = import ./nix/packages {
pkgs = pkgs;
sourceInfo = sourceInfoDogfood;
openclawToolPkgs = openclawToolPkgs;
inherit qmdPackage;
};
in
{
formatter = pkgs.nixfmt-tree.override {
settings = {
global.excludes = [ "nix/generated/openclaw-config-options.nix" ];
};
};
packages = packageSetStable // {
default = packageSetStable.openclaw;
openclaw-dogfood = packageSetDogfood.openclaw;
openclaw-gateway-dogfood = packageSetDogfood.openclaw-gateway;
};
apps = {
openclaw = flake-utils.lib.mkApp { drv = packageSetStable.openclaw-gateway; };
openclaw = flake-utils.lib.mkApp { drv = packageSetStable.openclaw; };
};
checks = {
gateway = packageSetStable.openclaw-gateway;
package-contents = pkgs.callPackage ./nix/checks/openclaw-package-contents.nix {
openclawGateway = packageSetStable.openclaw-gateway;
checks =
let
baseChecks = {
gateway = packageSetStable.openclaw-gateway;
bin-surface = pkgs.callPackage ./nix/checks/openclaw-bin-surface.nix {
openclawPackage = packageSetStable.openclaw;
};
package-contents = pkgs.callPackage ./nix/checks/openclaw-package-contents.nix {
openclawGateway = packageSetStable.openclaw-gateway;
};
package-contents-dogfood = pkgs.callPackage ./nix/checks/openclaw-package-contents.nix {
openclawGateway = packageSetDogfood.openclaw-gateway;
};
default-instance = pkgs.callPackage ./nix/checks/openclaw-default-instance.nix { };
config-validity = pkgs.callPackage ./nix/checks/openclaw-config-validity.nix {
openclawGateway = packageSetStable.openclaw-gateway;
};
gateway-smoke = pkgs.callPackage ./nix/checks/openclaw-gateway-smoke.nix {
openclawGateway = packageSetStable.openclaw-gateway;
};
}
// pkgs.lib.optionalAttrs (qmdPackage != null) {
qmd-runtime = pkgs.callPackage ./nix/checks/openclaw-qmd-runtime.nix {
openclawPackage = packageSetStable.openclaw;
inherit qmdPackage;
};
}
// (
if pkgs.stdenv.hostPlatform.isLinux then
let
sourceChecks = pkgs.callPackage ./nix/checks/openclaw-source-checks.nix {
sourceInfo = sourceInfoStable;
openclawGateway = packageSetStable.openclaw-gateway;
};
in
{
config-options = sourceChecks;
source-checks = sourceChecks;
hm-activation = import ./nix/checks/openclaw-hm-activation.nix {
inherit pkgs home-manager;
};
}
else
{ }
);
in
baseChecks
// {
# CI aggregator: build the expensive gateway once, then run all checks in the
# same build machine/store to avoid cache-miss races between parallel jobs.
ci = pkgs.symlinkJoin {
name = "nix-openclaw-ci";
paths = [
packageSetStable.openclaw
packageSetStable.openclaw-gateway
]
++ (builtins.attrValues baseChecks);
};
};
config-validity = pkgs.callPackage ./nix/checks/openclaw-config-validity.nix {
openclawGateway = packageSetStable.openclaw-gateway;
};
} // (if pkgs.stdenv.hostPlatform.isLinux then {
gateway-tests = pkgs.callPackage ./nix/checks/openclaw-gateway-tests.nix {
sourceInfo = sourceInfoStable;
};
config-options = pkgs.callPackage ./nix/checks/openclaw-config-options.nix {
sourceInfo = sourceInfoStable;
};
default-instance = pkgs.callPackage ./nix/checks/openclaw-default-instance.nix {};
hm-activation = import ./nix/checks/openclaw-hm-activation.nix {
inherit pkgs home-manager;
};
} else {});
devShells.default = pkgs.mkShell {
packages = [
pkgs.git
pkgs.nixfmt-rfc-style
pkgs.nixfmt-tree
pkgs.nil
];
};
}
) // {
)
// {
overlays.default = overlay;
templates.agent-first = {
path = ./templates/agent-first;
description = "Agent-first Home Manager setup for OpenClaw through Nix.";
};
nixosModules.openclaw-gateway = import ./nix/modules/nixos/openclaw-gateway.nix;
homeManagerModules.openclaw = import ./nix/modules/home-manager/openclaw.nix;
darwinModules.openclaw = import ./nix/modules/darwin/openclaw.nix;
};

15
garnix.yaml Normal file
View File

@ -0,0 +1,15 @@
builds:
include:
# CI aggregators prove package contracts.
- "checks.aarch64-darwin.ci"
- "checks.x86_64-linux.ci"
# User-facing/component packages must also be top-level Garnix artifacts,
# otherwise downstream machines can see green CI but miss the binary cache.
- "packages.aarch64-darwin.openclaw"
- "packages.aarch64-darwin.openclaw-dogfood"
- "packages.aarch64-darwin.openclaw-gateway"
- "packages.aarch64-darwin.openclaw-gateway-dogfood"
- "packages.x86_64-linux.openclaw"
- "packages.x86_64-linux.openclaw-dogfood"
- "packages.x86_64-linux.openclaw-gateway"
- "packages.x86_64-linux.openclaw-gateway-dogfood"

27
maintainers/AGENTS.md Normal file
View File

@ -0,0 +1,27 @@
# Maintainer Agent Guide
This directory is public maintainer guidance for agents working on `nix-openclaw`.
It is not consumer setup documentation and must not contain private deployment state.
## Boundaries
- Keep consumer onboarding in `README.md`, templates, and module docs.
- Keep private deployments, bots, hosts, local worktrees, tokens, and personal automation details out of this repo.
- If a private deployment exposes a public packaging bug, fix the public package here and keep deployment-specific repair elsewhere.
- Treat `README.md` as the product direction source of truth.
## Read Order
1. `packaging.md` for Nix-owned package invariants.
2. `release-policy.md` for the split-track publishing invariant.
3. `automation.md` for the maintainer repair loop.
4. `gates.md` for verification and CI expectations.
5. Root `AGENTS.md` for repo-wide rules.
## Maintainer Workflow
- Work on `main` by default and push small, surgical commits directly to `main` when maintainer policy allows it.
- Use branches only when a maintainer asks, direct push is blocked, or a disposable local experiment is needed.
- For multi-issue work, commit and push one issue at a time, then verify GitHub Actions for that pushed commit before continuing.
- Do not leave completed maintainer work parked on an agent branch.
- No force push. No weakening package checks just to get green.

47
maintainers/automation.md Normal file
View File

@ -0,0 +1,47 @@
# Maintainer Automation
Maintainer automation is an agentic repair loop for the public packaging pipeline. It is not a second release pipeline and not a private deployment monitor.
## Daily Objective
Answer first:
```text
Does nix-openclaw publish the latest upstream version for both supported tracks?
```
Answer `YES` only when:
- `openclaw-gateway` matches the newest stable upstream source release.
- `openclaw-app` matches the newest stable upstream release with a published public `OpenClaw-*.zip`.
If both tracks are current and yolo/CI are healthy, stop with a short CTO-level report:
- current gateway
- latest upstream gateway
- current app
- latest published app
- whether action was needed
## Repair Loop
If the desired state is not true, keep working until it is true or until the exact blocker is proven.
Diagnose across:
- upstream release data
- yolo selection
- pin materialization
- generated config options
- package builds
- smoke checks
- module activation
- workflow behavior
- caches
- CI runner failures
Do not ask for a repair strategy when the desired state is clear.
If the fix belongs in `nix-openclaw`, edit the repo, self-review the diff until there are no actionable findings, run the relevant targeted checks plus the full gate, commit directly to `main`, push directly to `main`, and verify GitHub Actions on the pushed commit.
If upstream has not published public macOS app assets, call that out directly, keep the app pin on the newest public zip, keep packaging the latest stable source-built gateway, and repair `nix-openclaw` only if it fails to do that.

22
maintainers/gates.md Normal file
View File

@ -0,0 +1,22 @@
# Gates
Use targeted checks while debugging, then run the full relevant gate before handoff.
## Required Checks
- `scripts/check-flake-lock-owners.sh`
- selector tests
- updater shell syntax
- workflow YAML parse
- `nix flake show --accept-flake-config`
- Linux CI aggregator
- Darwin CI aggregator when available
- `scripts/hm-activation-macos.sh` when a macOS runner is available
## CI Verification
After pushing maintainer fixes, verify the GitHub Actions run for the pushed commit.
Never say you will keep polling unless a blocking poll is already running. If reporting a poll, name the active run or local polling session.
If CI fails, inspect the failing run, classify the failure, fix what belongs to `nix-openclaw`, and rerun until green or until the exact external blocker is proven.

46
maintainers/packaging.md Normal file
View File

@ -0,0 +1,46 @@
# Packaging Invariants
This repo ships a working Nix package for OpenClaw users, not just a pin mirror.
## Product Surface
- The user-facing package is `openclaw`.
- `openclaw-gateway` is the source-built runnable gateway for Linux and macOS.
- `openclaw-app` is the Darwin-only desktop app from upstream's public app artifact.
- Component outputs exist for modules, checks, and debugging. They are not separate product tracks.
- `openclaw-dogfood` and `openclaw-gateway-dogfood` are temporary maintainer
artifacts for testing a specific upstream commit before the next stable
release. They must not become the documented consumer default.
- Do not split the repo into separate desktop and server tracks.
## Nix Ownership
- OpenClaw owns product and runtime behavior.
- `nix-openclaw` owns batteries-included Nix packaging, Home Manager/NixOS/Darwin modules, runtime PATH/env injection, launchd/systemd wiring, and package-contract checks.
- `nix-openclaw-tools` owns packaging OpenClaw-adjacent CLI tools and plugin metadata. Consume it here; do not duplicate its package definitions here.
- Downstream system repos should only choose hosts, secrets, accounts, and enabled plugins. If downstream needs bespoke scripts to make a plugin or harness work, prefer fixing this repo or `nix-openclaw-tools`.
- Nix mode means Nix owns `openclaw.json`.
- Runtime config mutation belongs upstream in OpenClaw. Downstream patches here must be small, temporary, and removed after the pinned upstream release contains the fix.
- Generated config options come from the upstream core schema.
- Plugin-owned extension surfaces, such as `channels.<plugin-id>`, must remain accepted by the Home Manager module even when core does not type every plugin key.
- Runtime tool injection belongs here. If a plugin or battery is enabled, the active OpenClaw harness must see its CLI tools and required environment without asking downstream to expose those tools globally on the user PATH.
- OpenClaw plugin roots belong here too. The Home Manager module consumes `openclawPlugin.plugins` declarations from plugin flakes and writes `plugins.load.paths` plus default `plugins.entries.<id>.enabled` values into the generated config.
- Raw npm/ClawHub plugin names are not batteries-included deployment config. Curated plugins packaged here must be exposed through packages/checks so CI/Garnix caches them. Arbitrary user specs need a deterministic lock/hash-backed Nix builder so Nix reuses the user's store/cache and only rebuilds when the spec, lock, or hash changes.
## Build Contract
- The gateway package must include Control UI assets.
- No inline scripts or inline file contents in Nix code. Use repo scripts and explicit file paths.
- Keep runtime tools internal to the `openclaw` wrapper unless they are intentionally part of the public package surface.
- QMD is the Nix-supported batteries-included local memory backend. Keep `qmd` internal to the `openclaw` wrapper PATH; users opt in with upstream config.
- ACPX is the first bundled OpenClaw plugin proof. It is consumed from OpenClaw's built `dist-runtime/extensions/acpx` tree, not installed or repaired by npm at runtime.
- Keep files under 400 lines unless a maintainer explicitly accepts the larger file.
## Investigations
### mcporter and QMD
- `mcporter` is an OpenClaw-owned optional MCP/CLI bridge, not a QMD requirement.
- OpenClaw defaults to direct `qmd` CLI execution. Keep that as the Nix-supported baseline until measured startup or per-query overhead proves otherwise.
- Package `mcporter` in `nix-openclaw-tools` as an optional tool when needed, but do not add it to the default `openclaw` runtime PATH just because QMD is bundled.
- If `memory.qmd.mcporter.enabled = true`, nix-openclaw should make `mcporter` visible to that instance and require the matching mcporter server config for `qmd mcp`.

View File

@ -0,0 +1,26 @@
# Release Policy
`nix-openclaw` publishes one user-facing package, `openclaw`, with component outputs for maintainers and modules.
## Desired State
- `openclaw-gateway` tracks the newest stable upstream OpenClaw source release that satisfies the Nix package contract.
- `openclaw-app` tracks the newest stable upstream release that has a published public `OpenClaw-*.zip` app artifact.
- These tracks are independent. Source and app versions may differ.
## Non-Negotiables
- Do not hold back the source-built gateway because a newer source release lacks public macOS app assets.
- Do not treat source/app version mismatch as a failure.
- Do not make upstream's full Vitest suite a promotion gate; upstream owns source test health.
- Do verify the Nix-owned package contract: source build, generated config options, package contents, gateway smoke startup, module activation, and newest available public macOS app artifact.
- Do prefer the upstream `.zip` app artifact for `openclaw-app`, but verify the unpacked contents contain an `.app`.
## Freshness Check
The package is fresh only when both are true:
- `nix/sources/openclaw-source.nix` matches GitHub's newest stable OpenClaw source tag.
- `nix/packages/openclaw-app.nix` matches the newest stable public `OpenClaw-*.zip` app artifact.
If newer stable source releases lack public app assets, report that as an upstream app publishing miss and keep the app pin on the newest public zip.

View File

@ -0,0 +1,22 @@
{
lib,
stdenvNoCC,
openclawPackage,
}:
stdenvNoCC.mkDerivation {
pname = "openclaw-bin-surface";
version = lib.getVersion openclawPackage;
dontUnpack = true;
dontConfigure = true;
dontBuild = true;
env = {
OPENCLAW_PACKAGE = openclawPackage;
};
doCheck = true;
checkPhase = "${../scripts/check-openclaw-bin-surface.sh}";
installPhase = "${../scripts/empty-install.sh}";
}

View File

@ -1,174 +0,0 @@
{ lib
, pkgs
, stdenv
, fetchFromGitHub
, fetchurl
, nodejs_22
, pnpm_10
, pkg-config
, jq
, python3
, node-gyp
, git
, zstd
, sourceInfo
, pnpmDepsHash ? (sourceInfo.pnpmDepsHash or null)
}:
let
linuxFirstParty = [
"summarize"
"gogcli"
"goplaces"
"camsnap"
"sonoscli"
"sag"
"oracle"
];
enableFirstParty = name: stdenv.hostPlatform.isDarwin || lib.elem name linuxFirstParty;
stubModule = { lib, ... }: {
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [];
};
home.homeDirectory = lib.mkOption {
type = lib.types.str;
default = "/tmp";
};
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [];
};
home.file = lib.mkOption {
type = lib.types.attrs;
default = {};
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = {};
};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = {};
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = {};
};
programs.git.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
lib = lib.mkOption {
type = lib.types.attrs;
default = {};
};
};
};
pluginEval = lib.evalModules {
modules = [
stubModule
../modules/home-manager/openclaw.nix
({ lib, options, ... }: {
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
lib.file.mkOutOfStoreSymlink = path: path;
programs.openclaw = {
enable = true;
launchd.enable = false;
systemd.enable = false;
instances.default = {};
bundledPlugins = lib.mapAttrs (name: _: { enable = enableFirstParty name; }) options.programs.openclaw.bundledPlugins;
};
};
})
];
specialArgs = { inherit pkgs; };
};
pluginEvalKey = builtins.deepSeq pluginEval.config.assertions "ok";
sourceFetch = lib.removeAttrs sourceInfo [ "pnpmDepsHash" ];
pnpmPlatform = if stdenv.hostPlatform.isDarwin then "darwin" else "linux";
pnpmArch = if stdenv.hostPlatform.isAarch64 then "arm64" else "x64";
nodeAddonApi = stdenv.mkDerivation {
pname = "node-addon-api";
version = "8.5.0";
src = fetchurl {
url = "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz";
hash = "sha256-0S8HyBYig7YhNVGFXx2o2sFiMxN0YpgwteZA8TDweRA=";
};
dontConfigure = true;
dontBuild = true;
installPhase = "${../scripts/node-addon-api-install.sh}";
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "openclaw-config-options";
version = "2026.1.8-2";
src = fetchFromGitHub sourceFetch;
pnpmDeps = pnpm_10.fetchDeps {
inherit (finalAttrs) pname version src;
hash = if pnpmDepsHash != null
then pnpmDepsHash
else lib.fakeHash;
fetcherVersion = 2;
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
nativeBuildInputs = [ git ];
};
nativeBuildInputs = [
nodejs_22
pnpm_10
pkg-config
jq
python3
node-gyp
zstd
];
env = {
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
PNPM_CONFIG_MANAGE_PACKAGE_MANAGER_VERSIONS = "false";
npm_config_nodedir = nodejs_22;
npm_config_python = python3;
NODE_PATH = "${nodeAddonApi}/lib/node_modules:${node-gyp}/lib/node_modules";
PNPM_DEPS = finalAttrs.pnpmDeps;
NODE_GYP_WRAPPER_SH = "${../scripts/node-gyp-wrapper.sh}";
GATEWAY_PREBUILD_SH = "${../scripts/gateway-prebuild.sh}";
PROMOTE_PNPM_INTEGRITY_SH = "${../scripts/promote-pnpm-integrity.sh}";
REMOVE_PACKAGE_MANAGER_FIELD_SH = "${../scripts/remove-package-manager-field.sh}";
STDENV_SETUP = "${stdenv}/setup";
CONFIG_OPTIONS_GENERATOR = "${../scripts/generate-config-options.ts}";
CONFIG_OPTIONS_GOLDEN = "${../generated/openclaw-config-options.nix}";
NODE_ENGINE_CHECK = "${../scripts/check-node-engine.ts}";
OPENCLAW_PLUGIN_EVAL = pluginEvalKey;
};
buildPhase = "${../scripts/gateway-tests-build.sh}";
postPatch = "${../scripts/gateway-postpatch.sh}";
doCheck = true;
checkPhase = "${../scripts/config-options-check.sh}";
installPhase = "${../scripts/empty-install.sh}";
dontPatchShebangs = true;
})

View File

@ -1,86 +1,94 @@
{ lib, pkgs, stdenv, nodejs_22, openclawGateway }:
{
lib,
pkgs,
stdenv,
nodejs_22,
openclawGateway,
}:
let
stubModule = { lib, ... }: {
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [];
};
stubModule =
{ lib, ... }:
{
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [ ];
};
home.homeDirectory = lib.mkOption {
type = lib.types.str;
default = "/tmp";
};
home.homeDirectory = lib.mkOption {
type = lib.types.str;
default = "/tmp";
};
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [];
};
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [ ];
};
home.file = lib.mkOption {
type = lib.types.attrs;
default = {};
};
home.file = lib.mkOption {
type = lib.types.attrs;
default = { };
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = {};
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = { };
};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = {};
};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = { };
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = {};
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = { };
};
programs.git.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
programs.git.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
lib = lib.mkOption {
type = lib.types.attrs;
default = {};
lib = lib.mkOption {
type = lib.types.attrs;
default = { };
};
};
};
};
moduleEval = lib.evalModules {
modules = [
stubModule
../modules/home-manager/openclaw.nix
({ lib, ... }: {
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
lib.file.mkOutOfStoreSymlink = path: path;
programs.openclaw = {
enable = true;
launchd.enable = false;
systemd.enable = false;
instances.default = {};
config = {
gateway = {
bind = "tailnet";
auth = {
mode = "token";
token = "test-token";
};
reload = {
mode = "hot";
debounceMs = 500;
(
{ lib, ... }:
{
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
lib.file.mkOutOfStoreSymlink = path: path;
programs.openclaw = {
enable = true;
launchd.enable = false;
systemd.enable = false;
instances.default = {
workspaceDir = expectedWorkspace;
config = {
channels.telegram = {
enabled = true;
botToken = "123456:test-token";
dmPolicy = "open";
groupPolicy = "disabled";
allowFrom = [ "*" ];
};
};
};
discovery.mdns.mode = "minimal";
};
};
};
})
}
)
];
specialArgs = { inherit pkgs; };
};
@ -88,6 +96,7 @@ let
configPathKey = ".openclaw/openclaw.json";
configJson = moduleEval.config.home.file."${configPathKey}".text;
configFile = pkgs.writeText "openclaw-config.json" configJson;
expectedWorkspace = "/tmp/openclaw-explicit-workspace";
in
stdenv.mkDerivation {
@ -102,7 +111,8 @@ stdenv.mkDerivation {
env = {
OPENCLAW_CONFIG_PATH = configFile;
OPENCLAW_SRC = "${openclawGateway}/lib/openclaw";
OPENCLAW_GATEWAY = openclawGateway;
OPENCLAW_EXPECTED_WORKSPACE = expectedWorkspace;
};
doCheck = true;

View File

@ -1,78 +1,368 @@
{ lib, pkgs, stdenv }:
{
lib,
pkgs,
stdenv,
}:
let
stubModule = { lib, ... }: {
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [];
testLib = lib.extend (
_final: _prev: {
hm.dag = {
entryAfter = after: data: {
inherit after data;
before = [ ];
};
};
}
);
home.homeDirectory = lib.mkOption {
type = lib.types.str;
default = "/tmp";
lockedPathFlake =
name: path: narHash:
let
# If a fixture changes, update with: nix hash path --sri nix/tests/plugins/<name>
storePath = builtins.path {
inherit name path;
sha256 = narHash;
};
in
"path:${builtins.unsafeDiscardStringContext (toString storePath)}?narHash=${narHash}";
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [];
};
alphaPluginSource =
lockedPathFlake "openclaw-test-plugin-alpha" ../tests/plugins/alpha
"sha256-FV4UN38sPy2Yp/HhqUxd0HW5l2PcIBBmUz4JzxTAOXY=";
betaPluginSource =
lockedPathFlake "openclaw-test-plugin-beta" ../tests/plugins/beta
"sha256-lDKtQKHZHqOkOprjLZzBEu8cFJhAdyEzsays9hdVeqE=";
runtimePluginSource =
lockedPathFlake "openclaw-test-plugin-runtime" ../tests/plugins/runtime
"sha256-Ytei4j076EQ5rcpoiMt4BhSGUMtlU5kohQ+CCfKwxEE=";
home.file = lib.mkOption {
type = lib.types.attrs;
default = {};
};
stubModule =
{ lib, ... }:
{
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [ ];
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = {};
};
home.homeDirectory = lib.mkOption {
type = lib.types.str;
default = "/tmp";
};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = {};
};
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [ ];
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = {};
};
home.file = lib.mkOption {
type = lib.types.attrs;
default = { };
};
programs.git.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = { };
};
lib = lib.mkOption {
type = lib.types.attrs;
default = {};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = { };
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = { };
};
programs.git.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
lib = lib.mkOption {
type = lib.types.attrs;
default = { };
};
};
};
};
eval = lib.evalModules {
modules = [
stubModule
../modules/home-manager/openclaw.nix
({ lib, ... }: {
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
lib.file.mkOutOfStoreSymlink = path: path;
programs.openclaw = {
enable = true;
launchd.enable = false;
systemd.enable = true;
};
};
})
moduleEval =
openclawConfig:
testLib.evalModules {
modules = [
stubModule
../modules/home-manager/openclaw.nix
(
{ lib, ... }:
{
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
lib.file.mkOutOfStoreSymlink = path: path;
programs.openclaw = {
enable = true;
launchd.enable = pkgs.stdenv.hostPlatform.isDarwin;
systemd.enable = pkgs.stdenv.hostPlatform.isLinux;
}
// openclawConfig;
};
}
)
];
specialArgs = { inherit pkgs; };
};
failedAssertions =
eval: lib.filter (assertion: !(assertion.assertion or false)) eval.config.assertions;
requireNoAssertionFailures =
name: eval:
let
failures = failedAssertions eval;
messages = map (assertion: assertion.message or "(no message)") failures;
in
if failures == [ ] then "ok" else throw "${name}: ${lib.concatStringsSep "; " messages}";
requireAssertionFailure =
name: needle: eval:
let
failures = failedAssertions eval;
matching = lib.filter (assertion: lib.hasInfix needle (assertion.message or "")) failures;
in
if matching != [ ] then "ok" else throw "${name}: expected assertion containing `${needle}`.";
defaultEval = moduleEval { };
defaultConfig = builtins.fromJSON defaultEval.config.home.file.".openclaw/openclaw.json".text;
hasLinuxUnit = builtins.hasAttr "openclaw-gateway" defaultEval.config.systemd.user.services;
hasDarwinAgent = builtins.hasAttr "com.steipete.openclaw.gateway" defaultEval.config.launchd.agents;
defaultCheck = builtins.deepSeq (requireNoAssertionFailures "default instance" defaultEval) (
if pkgs.stdenv.hostPlatform.isLinux && !hasLinuxUnit then
throw "Default OpenClaw instance missing systemd.unitName."
else if pkgs.stdenv.hostPlatform.isDarwin && !hasDarwinAgent then
throw "Default OpenClaw instance missing launchd.label."
else if (((defaultConfig.gateway or { }).mode or null) != "local") then
throw "Default OpenClaw instance missing gateway.mode."
else
"ok"
);
customPluginEval = moduleEval {
customPlugins = [
{ source = alphaPluginSource; }
];
specialArgs = { inherit pkgs; };
};
customPluginSkill = ".openclaw/workspace/skills/skill";
customPluginActivation = builtins.toJSON customPluginEval.config.home.activation.openclawWorkspaceFiles;
hasCustomPluginMaterializer = lib.hasInfix "openclaw-materialize-workspace-files" customPluginActivation;
customPluginCheck = builtins.deepSeq (requireNoAssertionFailures "customPlugins" customPluginEval) (
if hasCustomPluginMaterializer then
"ok"
else
throw "customPlugins did not wire workspace file materialization."
);
hasUnit = builtins.hasAttr "openclaw-gateway" eval.config.systemd.user.services;
check = if hasUnit then "ok" else throw "Default OpenClaw instance missing systemd.unitName.";
checkKey = builtins.deepSeq check "ok";
duplicateSkillEval = moduleEval {
customPlugins = [
{ source = alphaPluginSource; }
{ source = betaPluginSource; }
];
};
duplicateSkillCheck =
requireAssertionFailure "duplicate plugin skills"
"Duplicate skill paths detected: ${customPluginSkill}"
duplicateSkillEval;
userPluginSkillCollisionEval = moduleEval {
customPlugins = [
{ source = alphaPluginSource; }
];
skills = [
{
name = "skill";
mode = "inline";
}
];
};
userPluginSkillCollisionCheck =
requireAssertionFailure "user/plugin skill collision"
"Duplicate skill paths detected: ${customPluginSkill}"
userPluginSkillCollisionEval;
secretProviderEval = moduleEval {
config.secrets.providers.test-file = {
source = "file";
path = "/tmp/openclaw-secrets.json";
mode = "json";
};
};
secretProviderConfig =
builtins.fromJSON
secretProviderEval.config.home.file.".openclaw/openclaw.json".text;
secretProviderCheck =
builtins.deepSeq (requireNoAssertionFailures "secrets.providers" secretProviderEval)
(
if
((((secretProviderConfig.secrets or { }).providers or { }).test-file or { }).source == "file")
then
"ok"
else
throw "secrets.providers file variant missing from generated config."
);
qmdPrewarmEval = moduleEval {
qmd.prewarmModels.enable = true;
};
qmdPrewarmActivation = builtins.toJSON qmdPrewarmEval.config.home.activation.openclawQmdPrewarm;
qmdPrewarmCheck = builtins.deepSeq (requireNoAssertionFailures "qmd.prewarmModels" qmdPrewarmEval) (
if
lib.hasInfix "OPENCLAW_QMD_BIN=" qmdPrewarmActivation
&& lib.hasInfix "openclaw-qmd-prewarm.sh" qmdPrewarmActivation
then
"ok"
else
throw "qmd.prewarmModels did not wire QMD model-cache prewarm activation."
);
runtimeProfileEval = moduleEval {
runtimePackages = [ pkgs.jq ];
environment.OPENCLAW_TEST_SECRET = "/tmp/openclaw-secret";
};
runtimeProfileActivation = builtins.toJSON runtimeProfileEval.config.home.activation.openclawCodexRuntimeProfiles;
runtimeProfileCheck =
builtins.deepSeq (requireNoAssertionFailures "runtime profile" runtimeProfileEval)
(
if lib.hasInfix "openclaw-link-codex-runtime-profiles.sh" runtimeProfileActivation then
"ok"
else
throw "runtimePackages did not wire the Codex runtime profile activation."
);
openclawPluginEval = moduleEval {
customPlugins = [
{ source = runtimePluginSource; }
];
config.plugins.load.paths = [
"/tmp/user-openclaw-plugin"
];
};
openclawPluginConfig = builtins.fromJSON (
builtins.unsafeDiscardStringContext
openclawPluginEval.config.home.file.".openclaw/openclaw.json".text
);
openclawPluginLoadPaths = ((openclawPluginConfig.plugins or { }).load or { }).paths or [ ];
openclawPluginEntry = ((openclawPluginConfig.plugins or { }).entries or { }).runtime-test or { };
openclawPluginDisabledEntry =
((openclawPluginConfig.plugins or { }).entries or { }).runtime-disabled or null;
openclawPluginCheck =
builtins.deepSeq (requireNoAssertionFailures "OpenClaw plugin load" openclawPluginEval)
(
if !(lib.any (path: lib.hasSuffix "/plugin" path) openclawPluginLoadPaths) then
throw "OpenClaw plugin root was not added to plugins.load.paths."
else if !(lib.any (path: lib.hasSuffix "/disabled-plugin" path) openclawPluginLoadPaths) then
throw "OpenClaw plugin root with enabled=false was not added to plugins.load.paths."
else if !(lib.elem "/tmp/user-openclaw-plugin" openclawPluginLoadPaths) then
throw "User-defined plugins.load.paths entry was not preserved."
else if (openclawPluginEntry.enabled or false) != true then
throw "OpenClaw plugin entry default was not enabled."
else if (openclawPluginDisabledEntry.enabled or null) != false then
throw "OpenClaw plugin entry with enabled=false did not render a disabled default."
else
"ok"
);
openclawPluginOverrideEval = moduleEval {
customPlugins = [
{ source = runtimePluginSource; }
];
config.plugins.entries.runtime-test.enabled = false;
};
openclawPluginOverrideConfig = builtins.fromJSON (
builtins.unsafeDiscardStringContext
openclawPluginOverrideEval.config.home.file.".openclaw/openclaw.json".text
);
openclawPluginOverrideEntry =
((openclawPluginOverrideConfig.plugins or { }).entries or { }).runtime-test or { };
openclawPluginOverrideDisabledEntry =
((openclawPluginOverrideConfig.plugins or { }).entries or { }).runtime-disabled or { };
openclawPluginOverrideCheck =
builtins.deepSeq (requireNoAssertionFailures "OpenClaw plugin override" openclawPluginOverrideEval)
(
if (openclawPluginOverrideEntry.enabled or null) != false then
throw "User config could not override OpenClaw plugin enabled default."
else if (openclawPluginOverrideDisabledEntry.enabled or null) != false then
throw "Plugin enabled=false default did not survive when not overridden."
else
"ok"
);
openclawPluginEnableOverrideEval = moduleEval {
customPlugins = [
{ source = runtimePluginSource; }
];
config.plugins.entries.runtime-disabled.enabled = true;
};
openclawPluginEnableOverrideConfig = builtins.fromJSON (
builtins.unsafeDiscardStringContext
openclawPluginEnableOverrideEval.config.home.file.".openclaw/openclaw.json".text
);
openclawPluginEnableOverrideEntry =
((openclawPluginEnableOverrideConfig.plugins or { }).entries or { }).runtime-disabled or { };
openclawPluginEnableOverrideCheck =
builtins.deepSeq
(requireNoAssertionFailures "OpenClaw plugin enable override" openclawPluginEnableOverrideEval)
(
if (openclawPluginEnableOverrideEntry.enabled or null) == true then
"ok"
else
throw "User config could not override OpenClaw plugin enabled=false default."
);
npmRuntimePluginEval = moduleEval {
customPlugins = [
{
source = "npm:@tencent-weixin/openclaw-weixin@2.4.2";
id = "openclaw-weixin";
hash = lib.fakeHash;
}
];
};
npmRuntimePluginConfig = builtins.fromJSON (
builtins.unsafeDiscardStringContext
npmRuntimePluginEval.config.home.file.".openclaw/openclaw.json".text
);
npmRuntimePluginLoadPaths = ((npmRuntimePluginConfig.plugins or { }).load or { }).paths or [ ];
npmRuntimePluginEntry =
((npmRuntimePluginConfig.plugins or { }).entries or { }).openclaw-weixin or { };
npmRuntimePluginCheck =
builtins.deepSeq (requireNoAssertionFailures "npm OpenClaw runtime plugin" npmRuntimePluginEval)
(
if
!(lib.any (
path: lib.hasInfix "openclaw-runtime-plugin-openclaw-weixin" path
) npmRuntimePluginLoadPaths)
then
throw "npm OpenClaw runtime plugin root was not added to plugins.load.paths."
else if (npmRuntimePluginEntry.enabled or false) != true then
throw "npm OpenClaw runtime plugin entry default was not enabled."
else
"ok"
);
checkKey = builtins.deepSeq [
defaultCheck
customPluginCheck
duplicateSkillCheck
userPluginSkillCollisionCheck
secretProviderCheck
qmdPrewarmCheck
runtimeProfileCheck
openclawPluginCheck
openclawPluginOverrideCheck
openclawPluginEnableOverrideCheck
npmRuntimePluginCheck
] "ok";
in
stdenv.mkDerivation {

View File

@ -0,0 +1,27 @@
{
lib,
stdenv,
nodejs_22,
openclawGateway,
}:
stdenv.mkDerivation {
pname = "openclaw-gateway-smoke";
version = lib.getVersion openclawGateway;
dontUnpack = true;
dontConfigure = true;
dontBuild = true;
nativeBuildInputs = [ nodejs_22 ];
env = {
OPENCLAW_GATEWAY = openclawGateway;
};
__darwinAllowLocalNetworking = true;
doCheck = true;
checkPhase = "${nodejs_22}/bin/node ${../scripts/gateway-smoke.mjs}";
installPhase = "${../scripts/empty-install.sh}";
}

View File

@ -1,90 +0,0 @@
{ lib
, stdenv
, fetchFromGitHub
, fetchurl
, nodejs_22
, pnpm_10
, bun
, pkg-config
, jq
, python3
, node-gyp
, vips
, git
, zstd
, sourceInfo
, pnpmDepsHash ? (sourceInfo.pnpmDepsHash or null)
}:
let
sourceFetch = lib.removeAttrs sourceInfo [ "pnpmDepsHash" ];
pnpmPlatform = if stdenv.hostPlatform.isDarwin then "darwin" else "linux";
pnpmArch = if stdenv.hostPlatform.isAarch64 then "arm64" else "x64";
nodeAddonApi = stdenv.mkDerivation {
pname = "node-addon-api";
version = "8.5.0";
src = fetchurl {
url = "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz";
hash = "sha256-0S8HyBYig7YhNVGFXx2o2sFiMxN0YpgwteZA8TDweRA=";
};
dontConfigure = true;
dontBuild = true;
installPhase = "${../scripts/node-addon-api-install.sh}";
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "openclaw-gateway-tests";
version = "2026.1.8-2";
src = fetchFromGitHub sourceFetch;
pnpmDeps = pnpm_10.fetchDeps {
inherit (finalAttrs) pname version src;
hash = if pnpmDepsHash != null
then pnpmDepsHash
else lib.fakeHash;
fetcherVersion = 2;
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
nativeBuildInputs = [ git ];
};
nativeBuildInputs = [
nodejs_22
pnpm_10
bun
pkg-config
jq
python3
node-gyp
zstd
];
buildInputs = [ vips ];
env = {
SHARP_IGNORE_GLOBAL_LIBVIPS = "1";
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
PNPM_CONFIG_MANAGE_PACKAGE_MANAGER_VERSIONS = "false";
npm_config_nodedir = nodejs_22;
npm_config_python = python3;
NODE_PATH = "${nodeAddonApi}/lib/node_modules:${node-gyp}/lib/node_modules";
PNPM_DEPS = finalAttrs.pnpmDeps;
NODE_GYP_WRAPPER_SH = "${../scripts/node-gyp-wrapper.sh}";
GATEWAY_PREBUILD_SH = "${../scripts/gateway-prebuild.sh}";
PROMOTE_PNPM_INTEGRITY_SH = "${../scripts/promote-pnpm-integrity.sh}";
REMOVE_PACKAGE_MANAGER_FIELD_SH = "${../scripts/remove-package-manager-field.sh}";
STDENV_SETUP = "${stdenv}/setup";
};
postPatch = "${../scripts/gateway-postpatch.sh}";
buildPhase = "${../scripts/gateway-tests-build.sh}";
doCheck = true;
checkPhase = "${../scripts/gateway-tests-check.sh}";
installPhase = "${../scripts/empty-install.sh}";
dontPatchShebangs = true;
})

View File

@ -3,12 +3,25 @@
let
openclawModule = ../modules/home-manager/openclaw.nix;
testScript = builtins.readFile ../tests/hm-activation.py;
lockedPathFlake =
name: path: narHash:
let
storePath = builtins.path {
inherit name path;
sha256 = narHash;
};
in
"path:${builtins.unsafeDiscardStringContext (toString storePath)}?narHash=${narHash}";
alphaPluginSource =
lockedPathFlake "openclaw-test-plugin-alpha" ../tests/plugins/alpha
"sha256-FV4UN38sPy2Yp/HhqUxd0HW5l2PcIBBmUz4JzxTAOXY=";
in
pkgs.testers.nixosTest {
name = "openclaw-hm-activation";
nodes.machine = { ... }:
nodes.machine =
{ ... }:
{
imports = [ home-manager.nixosModules.home-manager ];
@ -23,7 +36,8 @@ pkgs.testers.nixosTest {
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
users.alice = { lib, ... }:
users.alice =
{ lib, ... }:
{
imports = [ openclawModule ];
@ -35,6 +49,10 @@ pkgs.testers.nixosTest {
programs.openclaw = {
enable = true;
documents = ../tests/documents;
customPlugins = [
{ source = alphaPluginSource; }
];
installApp = false;
launchd.enable = false;
instances.default = {
@ -65,7 +83,7 @@ pkgs.testers.nixosTest {
"OPENCLAW_SKIP_CRON=1"
"OPENCLAW_SKIP_GMAIL_WATCHER=1"
"OPENCLAW_DISABLE_BONJOUR=1"
"NODE_OPTIONS=--report-on-fatalerror --report-on-signal --report-signal=SIGABRT"
"NODE_OPTIONS=--report-on-fatalerror"
"NODE_REPORT_DIRECTORY=/tmp/openclaw"
"NODE_REPORT_FILENAME=node-report.%p.json"
];

View File

@ -1,4 +1,9 @@
{ lib, stdenv, openclawGateway }:
{
lib,
stdenv,
nodejs_22,
openclawGateway,
}:
stdenv.mkDerivation {
pname = "openclaw-package-contents";
@ -13,6 +18,7 @@ stdenv.mkDerivation {
};
doCheck = true;
nativeCheckInputs = [ nodejs_22 ];
checkPhase = "${../scripts/check-package-contents.sh}";
installPhase = "${../scripts/empty-install.sh}";
}

View File

@ -0,0 +1,24 @@
{
lib,
stdenvNoCC,
openclawPackage,
qmdPackage ? null,
}:
stdenvNoCC.mkDerivation {
pname = "openclaw-qmd-runtime";
version = lib.getVersion openclawPackage;
dontUnpack = true;
dontConfigure = true;
dontBuild = true;
env = {
OPENCLAW_PACKAGE = openclawPackage;
QMD_PACKAGE = lib.optionalString (qmdPackage != null) "${qmdPackage}";
};
doCheck = true;
checkPhase = "${../scripts/check-openclaw-qmd-runtime.sh}";
installPhase = "${../scripts/empty-install.sh}";
}

View File

@ -0,0 +1,170 @@
{
lib,
pkgs,
stdenv,
fetchFromGitHub,
fetchurl,
nodejs_22,
pnpm_10,
fetchPnpmDeps,
pkg-config,
jq,
python3,
node-gyp,
vips,
git,
zstd,
sourceInfo,
openclawGateway,
pnpmDepsHash ? (sourceInfo.pnpmDepsHash or null),
}:
let
pluginCatalog = import ../modules/home-manager/openclaw/plugin-catalog.nix;
linuxBundledPlugins = builtins.attrNames (
lib.filterAttrs (_: plugin: plugin.linux or false) pluginCatalog
);
enableBundledPlugin = name: stdenv.hostPlatform.isDarwin || lib.elem name linuxBundledPlugins;
stubModule =
{ lib, ... }:
{
options = {
assertions = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [ ];
};
home.homeDirectory = lib.mkOption {
type = lib.types.str;
default = "/tmp";
};
home.packages = lib.mkOption {
type = lib.types.listOf lib.types.anything;
default = [ ];
};
home.file = lib.mkOption {
type = lib.types.attrs;
default = { };
};
home.activation = lib.mkOption {
type = lib.types.attrs;
default = { };
};
launchd.agents = lib.mkOption {
type = lib.types.attrs;
default = { };
};
systemd.user.services = lib.mkOption {
type = lib.types.attrs;
default = { };
};
programs.git.enable = lib.mkOption {
type = lib.types.bool;
default = false;
};
lib = lib.mkOption {
type = lib.types.attrs;
default = { };
};
};
};
pluginEval = lib.evalModules {
modules = [
stubModule
../modules/home-manager/openclaw.nix
(
{ lib, options, ... }:
{
config = {
home.homeDirectory = "/tmp";
programs.git.enable = false;
lib.file.mkOutOfStoreSymlink = path: path;
programs.openclaw = {
enable = true;
launchd.enable = false;
systemd.enable = false;
instances.default = { };
bundledPlugins = lib.mapAttrs (name: _: {
enable = enableBundledPlugin name;
}) options.programs.openclaw.bundledPlugins;
};
};
}
)
];
specialArgs = { inherit pkgs; };
};
pluginEvalKey = builtins.deepSeq pluginEval.config.assertions "ok";
common =
import ../lib/openclaw-gateway-common.nix
{
inherit
lib
stdenv
fetchFromGitHub
fetchurl
nodejs_22
pnpm_10
fetchPnpmDeps
pkg-config
jq
python3
node-gyp
git
zstd
;
}
{
pname = "openclaw-source-checks";
sourceInfo = sourceInfo;
pnpmDepsHash = pnpmDepsHash;
pnpmDepsPname = "openclaw-gateway";
enableSharp = true;
extraBuildInputs = [ vips ];
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "openclaw-source-checks";
inherit (common) version;
src = common.resolvedSrc;
pnpmDeps = common.pnpmDeps;
nativeBuildInputs = common.nativeBuildInputs;
buildInputs = common.buildInputs;
env = common.env // {
PNPM_DEPS = finalAttrs.pnpmDeps;
OPENCLAW_GATEWAY = openclawGateway;
CONFIG_OPTIONS_GENERATOR = "${../scripts/generate-config-options.ts}";
CONFIG_OPTIONS_GOLDEN = "${../generated/openclaw-config-options.nix}";
CONFIG_OPTIONS_CHECK_SH = "${../scripts/config-options-check.sh}";
NODE_ENGINE_CHECK = "${../scripts/check-node-engine.ts}";
OPENCLAW_PLUGIN_EVAL = pluginEvalKey;
OPENCLAW_SCHEMA_REV = sourceInfo.rev;
};
passthru = common.passthru;
postPatch = "${../scripts/gateway-postpatch.sh}";
buildPhase = "${../scripts/source-checks-build.sh}";
doCheck = true;
checkPhase = "${../scripts/source-checks-check.sh}";
installPhase = "${../scripts/empty-install.sh}";
dontPatchShebangs = true;
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
{
lib,
stdenvNoCC,
nodejs_22,
}:
{
id,
source,
hash ? lib.fakeHash,
}:
let
npmSpec =
if lib.hasPrefix "npm:" source then
lib.removePrefix "npm:" source
else
throw "OpenClaw runtime npm plugin source must start with `npm:`: ${source}";
safeName = lib.replaceStrings [ "@" "/" ":" ] [ "" "-" "-" ] id;
in
stdenvNoCC.mkDerivation {
pname = "openclaw-runtime-plugin-${safeName}";
version = "1";
nativeBuildInputs = [ nodejs_22 ];
dontUnpack = true;
dontConfigure = true;
dontBuild = true;
outputHashMode = "recursive";
outputHashAlgo = "sha256";
outputHash = hash;
env = {
OPENCLAW_RUNTIME_PLUGIN_ID = id;
OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC = npmSpec;
};
installPhase = "${../scripts/npm-runtime-plugin-install.sh}";
meta = with lib; {
description = "Nix-packaged OpenClaw runtime plugin ${id} from ${source}";
homepage = "https://github.com/openclaw/openclaw";
license = licenses.mit;
platforms = platforms.darwin ++ platforms.linux;
};
}

View File

@ -0,0 +1,140 @@
{
lib,
stdenv,
fetchFromGitHub,
fetchurl,
nodejs_22,
pnpm_10,
fetchPnpmDeps,
pkg-config,
jq,
python3,
node-gyp,
git,
zstd,
}:
# Shared build plumbing for OpenClaw gateway-related derivations.
#
# Goals:
# - one source of truth for pnpm deps fetch + common env
# - keep the individual derivations small/boring
{
pname,
sourceInfo,
pnpmDepsHash ? (sourceInfo.pnpmDepsHash or null),
pnpmDepsPname ? "openclaw-gateway",
gatewaySrc ? null,
src ? null,
enableSharp ? false,
extraNativeBuildInputs ? [ ],
extraBuildInputs ? [ ],
extraEnv ? { },
}:
let
sourceFetch = lib.removeAttrs sourceInfo [
"pnpmDepsHash"
"releaseTag"
"releaseVersion"
"applyPublicSurfaceHardlinksPatch"
"applySkipPluginAutoEnableNixModePatch"
"publicSurfaceHardlinksPatch"
"fsSafeSource"
];
# Prefer nixpkgs' platform mapping instead of hand-rolled arch/platform.
pnpmPlatform = stdenv.hostPlatform.node.platform;
pnpmArch = stdenv.hostPlatform.node.arch;
revShort = lib.substring 0 8 sourceInfo.rev;
version = "unstable-${revShort}";
resolvedSrc =
if src != null then
src
else if gatewaySrc != null then
gatewaySrc
else
fetchFromGitHub sourceFetch;
fsSafeSource = if sourceInfo ? fsSafeSource then fetchFromGitHub sourceInfo.fsSafeSource else null;
publicSurfaceHardlinksPatch =
sourceInfo.publicSurfaceHardlinksPatch or ../patches/allow-package-public-surface-hardlinks.patch;
nodeAddonApi = import ../packages/node-addon-api.nix { inherit stdenv fetchurl; };
pnpmDeps = fetchPnpmDeps {
pname = pnpmDepsPname;
inherit version;
src = resolvedSrc;
pnpm = pnpm_10;
hash = if pnpmDepsHash != null then pnpmDepsHash else lib.fakeHash;
fetcherVersion = 3;
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
nativeBuildInputs = [ git ];
};
envBase = {
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
PNPM_CONFIG_MANAGE_PACKAGE_MANAGER_VERSIONS = "false";
npm_config_nodedir = nodejs_22;
npm_config_python = python3;
NODE_PATH = "${nodeAddonApi}/lib/node_modules:${node-gyp}/lib/node_modules";
PNPM_DEPS = pnpmDeps;
OPENCLAW_BUILD_ROOT_SH = "${../scripts/build-root.sh}";
NODE_GYP_WRAPPER_SH = "${../scripts/node-gyp-wrapper.sh}";
GATEWAY_PREBUILD_SH = "${../scripts/gateway-prebuild.sh}";
PATCH_BUNDLED_RUNTIME_DEPS_SCRIPT = "${../patches/stage-bundled-plugin-runtime-deps.mjs}";
PATCH_PUBLIC_SURFACE_HARDLINKS =
if sourceInfo.applyPublicSurfaceHardlinksPatch or true then
"${publicSurfaceHardlinksPatch}"
else
"";
PATCH_SKIP_PLUGIN_AUTO_ENABLE_NIX_MODE =
if sourceInfo.applySkipPluginAutoEnableNixModePatch or true then
"${../patches/skip-plugin-auto-enable-persist-in-nix-mode.patch}"
else
"";
PROMOTE_PNPM_INTEGRITY_SH = "${../scripts/promote-pnpm-integrity.sh}";
REMOVE_PACKAGE_MANAGER_FIELD_SH = "${../scripts/remove-package-manager-field.sh}";
STDENV_SETUP = "${stdenv}/setup";
}
// lib.optionalAttrs (fsSafeSource != null) {
OPENCLAW_FS_SAFE_SOURCE = fsSafeSource;
};
in
{
inherit
version
pnpmDeps
resolvedSrc
pnpmPlatform
pnpmArch
nodeAddonApi
;
nativeBuildInputs = [
nodejs_22
pnpm_10
pkg-config
jq
python3
node-gyp
zstd
]
++ extraNativeBuildInputs;
buildInputs = extraBuildInputs;
env = envBase // (lib.optionalAttrs enableSharp { SHARP_IGNORE_GLOBAL_LIBVIPS = "1"; }) // extraEnv;
passthru = {
inherit sourceInfo pnpmDeps;
pinnedRev = sourceInfo.rev;
};
}

View File

@ -2,16 +2,14 @@
set -euo pipefail
link_agent() {
local target="$1"
local label="$2"
local label="$1"
local target="$HOME/Library/LaunchAgents/${label}.plist"
local candidate
local candidate=""
local hm_gen
hm_gen="$(realpath "$HOME/.local/state/nix/profiles/home-manager" 2>/dev/null || true)"
if [ -n "$hm_gen" ] && [ -e "$hm_gen/LaunchAgents/${label}.plist" ]; then
candidate="$hm_gen/LaunchAgents/${label}.plist"
else
candidate="$(/bin/ls -t /nix/store/*${label}.plist 2>/dev/null | /usr/bin/head -n 1 || true)"
fi
if [ -z "$candidate" ]; then
@ -22,6 +20,7 @@ link_agent() {
current="$(/usr/bin/readlink "$target" 2>/dev/null || true)"
if [ "$current" != "$candidate" ]; then
/bin/mkdir -p "${target%/*}"
/bin/ln -sfn "$candidate" "$target"
/bin/launchctl bootout "gui/$UID" "$target" 2>/dev/null || true
/bin/launchctl bootstrap "gui/$UID" "$target" 2>/dev/null || true
@ -30,14 +29,6 @@ link_agent() {
/bin/launchctl kickstart -k "gui/$UID/$label" 2>/dev/null || true
}
link_agent "$HOME/Library/LaunchAgents/com.steipete.openclaw.gateway.nix.plist" \
"com.steipete.openclaw.gateway.nix"
link_agent "$HOME/Library/LaunchAgents/com.steipete.openclaw.gateway.nix-test.plist" \
"com.steipete.openclaw.gateway.nix-test"
link_agent "$HOME/Library/LaunchAgents/com.steipete.openclaw.gateway.prod.plist" \
"com.steipete.openclaw.gateway.prod"
link_agent "$HOME/Library/LaunchAgents/com.steipete.openclaw.gateway.test.plist" \
"com.steipete.openclaw.gateway.test"
for label in "$@"; do
link_agent "$label"
done

View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
manifest=$1
while IFS=$'\t' read -r profile_dir bin_dir; do
[ -n "$profile_dir" ] || continue
mkdir -p "$profile_dir"
link="$profile_dir/bin"
if [ -L "$link" ]; then
rm "$link"
fi
if [ -e "$link" ]; then
echo "Refusing to replace non-symlink Codex runtime bin: $link" >&2
exit 1
fi
ln -s "$bin_dir" "$link"
done < "$manifest"

View File

@ -0,0 +1,42 @@
#!/bin/sh
set -eu
if [ "$#" -ne 2 ]; then
echo "usage: openclaw-materialize-workspace-files <state-manifest> <source-target-manifest>" >&2
exit 1
fi
manifest="$1"
source_manifest="$2"
manifest_dir="$(dirname "$manifest")"
mkdir -p "$manifest_dir"
new_manifest="$(mktemp)"
trap 'rm -f "$new_manifest"' EXIT
copy_path() {
source="$1"
target="$2"
if [ -e "$target" ] || [ -L "$target" ]; then
chmod -R u+w "$target" 2>/dev/null || true
rm -rf "$target"
fi
mkdir -p "$(dirname "$target")"
if [ -d "$source" ]; then
cp -RL "$source" "$target"
else
cp -L "$source" "$target"
fi
printf '%s\n' "$target" >> "$new_manifest"
}
while IFS="$(printf '\t')" read -r source target; do
if [ -n "$source" ] && [ -n "$target" ]; then
copy_path "$source" "$target"
fi
done < "$source_manifest"
sort -u "$new_manifest" > "$manifest"

View File

@ -1,10 +1,16 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
openclawLib = import ./lib.nix { inherit config lib pkgs; };
cfg = openclawLib.cfg;
homeDir = openclawLib.homeDir;
appPackage = openclawLib.appPackage;
qmdPackage = openclawLib.qmdPackage;
defaultInstance = {
enable = cfg.enable;
@ -16,10 +22,12 @@ let
gatewayPort = 18789;
gatewayPath = null;
gatewayPnpmDepsHash = lib.fakeHash;
runtimePackages = [ ];
environment = { };
launchd = cfg.launchd;
systemd = cfg.systemd;
plugins = openclawLib.effectivePlugins;
config = {};
config = { };
appDefaults = {
enable = true;
attachExistingOnly = true;
@ -32,20 +40,37 @@ let
};
};
instances = if cfg.instances != {}
then cfg.instances
else lib.optionalAttrs cfg.enable { default = defaultInstance; };
instances =
if cfg.instances != { } then
cfg.instances
else
lib.optionalAttrs cfg.enable { default = defaultInstance; };
enabledInstances = lib.filterAttrs (_: inst: inst.enable) instances;
plugins = import ./plugins.nix { inherit lib pkgs openclawLib enabledInstances; };
files = import ./files.nix {
inherit config lib pkgs openclawLib enabledInstances plugins;
plugins = import ./plugins.nix {
inherit
lib
pkgs
openclawLib
enabledInstances
;
};
stripNulls = value:
if value == null then null
files = import ./files.nix {
inherit
lib
pkgs
openclawLib
enabledInstances
plugins
;
};
stripNulls =
value:
if value == null then
null
else if builtins.isAttrs value then
lib.filterAttrs (_: v: v != null) (builtins.mapAttrs (_: stripNulls) value)
else if builtins.isList value then
@ -59,154 +84,253 @@ let
};
};
mkInstanceConfig = name: inst: let
gatewayPackage =
if inst.gatewayPath != null then
pkgs.callPackage ../../packages/openclaw-gateway.nix {
gatewaySrc = builtins.path {
path = inst.gatewayPath;
name = "openclaw-gateway-src";
};
pnpmDepsHash = inst.gatewayPnpmDepsHash;
}
else
inst.package;
pluginPackages = plugins.pluginPackagesFor name;
pluginEnvAll = plugins.pluginEnvAllFor name;
mergedConfig0 = stripNulls (lib.recursiveUpdate (lib.recursiveUpdate baseConfig cfg.config) inst.config);
existingWorkspace = (((mergedConfig0.agents or {}).defaults or {}).workspace or null);
mergedConfig =
if (cfg.workspace.pinAgentDefaults or true) && existingWorkspace == null then
lib.recursiveUpdate mergedConfig0 { agents = { defaults = { workspace = inst.workspaceDir; }; }; }
else
mergedConfig0;
configJson = builtins.toJSON mergedConfig;
configFile = pkgs.writeText "openclaw-${name}.json" configJson;
gatewayWrapper = pkgs.writeShellScriptBin "openclaw-gateway-${name}" ''
set -euo pipefail
if [ -n "${lib.makeBinPath pluginPackages}" ]; then
export PATH="${lib.makeBinPath pluginPackages}:$PATH"
fi
${lib.concatStringsSep "\n" (map (entry:
let
isFile = lib.hasSuffix "_FILE" entry.key;
in ''
if [ -f "${entry.value}" ]; then
if ${if isFile then "true" else "false"}; then
export ${entry.key}="${entry.value}"
else
rawValue="$("${lib.getExe' pkgs.coreutils "cat"}" "${entry.value}")"
if [ "''${rawValue#${entry.key}=}" != "$rawValue" ]; then
export ${entry.key}="''${rawValue#${entry.key}=}"
else
export ${entry.key}="$rawValue"
fi
fi
mkInstanceConfig =
name: inst:
let
gatewayPackage =
if inst.gatewayPath != null then
pkgs.callPackage ../../packages/openclaw-gateway.nix {
gatewaySrc = builtins.path {
path = inst.gatewayPath;
name = "openclaw-gateway-src";
};
pnpmDepsHash = inst.gatewayPnpmDepsHash;
}
else
export ${entry.key}="${entry.value}"
fi
'') pluginEnvAll)}
exec "${gatewayPackage}/bin/openclaw" "$@"
'';
appDefaults = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.appDefaults.enable) {
attachExistingOnly = inst.appDefaults.attachExistingOnly;
gatewayPort = inst.gatewayPort;
nixMode = inst.appDefaults.nixMode;
};
appInstall = if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then
null
else {
name = lib.removePrefix "${homeDir}/" inst.app.install.path;
value = {
source = "${appPackage}/Applications/OpenClaw.app";
recursive = true;
force = true;
inst.package;
pluginPackages = plugins.pluginPackagesFor name;
runtimePackages = lib.unique (
openclawLib.toolSets.tools
++ (lib.optional (qmdPackage != null) qmdPackage)
++ pluginPackages
++ cfg.runtimePackages
++ inst.runtimePackages
);
runtimeProfile = pkgs.symlinkJoin {
name = "openclaw-runtime-${name}";
paths = runtimePackages;
};
};
runtimePath = lib.makeBinPath runtimePackages;
runtimeEnvAll =
(plugins.pluginEnvAllFor name)
++ (lib.mapAttrsToList (key: value: {
inherit key value;
plugin = "runtime";
}) (cfg.environment // inst.environment));
userConfig = stripNulls (lib.recursiveUpdate (stripNulls cfg.config) (stripNulls inst.config));
pluginEntryConfig = plugins.openclawPluginEntriesConfigFor name;
openclawPluginLoadPaths = plugins.openclawPluginLoadPathsFor name;
mergedConfigWithoutLoadPaths = stripNulls (
lib.recursiveUpdate (lib.recursiveUpdate baseConfig pluginEntryConfig) userConfig
);
existingOpenClawPluginLoadPaths = (
((mergedConfigWithoutLoadPaths.plugins or { }).load or { }).paths or [ ]
);
mergedConfig0 =
if openclawPluginLoadPaths == [ ] then
mergedConfigWithoutLoadPaths
else
lib.recursiveUpdate mergedConfigWithoutLoadPaths {
plugins = {
load = {
paths = lib.unique (openclawPluginLoadPaths ++ existingOpenClawPluginLoadPaths);
};
};
};
existingWorkspace = (((mergedConfig0.agents or { }).defaults or { }).workspace or null);
mergedConfig =
if (cfg.workspace.pinAgentDefaults or true) && existingWorkspace == null then
lib.recursiveUpdate mergedConfig0 {
agents = {
defaults = {
workspace = inst.workspaceDir;
};
};
}
else
mergedConfig0;
configJson = builtins.toJSON mergedConfig;
configFile = pkgs.writeText "openclaw-${name}.json" configJson;
agentIds =
let
agents = ((mergedConfig.agents or { }).list or [ ]);
configured = lib.filter (id: id != null) (map (agent: agent.id or null) agents);
in
lib.unique ([ "main" ] ++ configured);
codexRuntimeProfiles = map (
agentId: "${inst.stateDir}/agents/${agentId}/agent/codex-home/home/.nix-profile"
) agentIds;
gatewayWrapper = pkgs.writeShellScriptBin "openclaw-gateway-${name}" ''
set -euo pipefail
package = gatewayPackage;
in {
homeFile = {
name = openclawLib.toRelative inst.configPath;
value = { text = configJson; };
};
configFile = configFile;
configPath = inst.configPath;
if [ -n "${runtimePath}" ]; then
export PATH="${runtimePath}:$PATH"
fi
dirs = [ inst.stateDir inst.workspaceDir (builtins.dirOf inst.logPath) ];
${lib.concatStringsSep "\n" (
map (
entry:
let
isFile = lib.hasSuffix "_FILE" entry.key;
in
''
if [ -f "${entry.value}" ]; then
if ${if isFile then "true" else "false"}; then
export ${entry.key}="${entry.value}"
else
rawValue="$("${lib.getExe' pkgs.coreutils "cat"}" "${entry.value}")"
if [ "''${rawValue#${entry.key}=}" != "$rawValue" ]; then
export ${entry.key}="''${rawValue#${entry.key}=}"
else
export ${entry.key}="$rawValue"
fi
fi
else
export ${entry.key}="${entry.value}"
fi
''
) runtimeEnvAll
)}
launchdAgent = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable) {
"${inst.launchd.label}" = {
enable = true;
config = {
Label = inst.launchd.label;
ProgramArguments = [
"${gatewayWrapper}/bin/openclaw-gateway-${name}"
"gateway"
"--port"
"${toString inst.gatewayPort}"
];
RunAtLoad = true;
KeepAlive = true;
WorkingDirectory = inst.stateDir;
StandardOutPath = inst.logPath;
StandardErrorPath = inst.logPath;
EnvironmentVariables = {
HOME = homeDir;
OPENCLAW_CONFIG_PATH = inst.configPath;
OPENCLAW_STATE_DIR = inst.stateDir;
OPENCLAW_IMAGE_BACKEND = "sips";
OPENCLAW_NIX_MODE = "1";
exec "${gatewayPackage}/bin/openclaw" "$@"
'';
appDefaults = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.appDefaults.enable) {
attachExistingOnly = inst.appDefaults.attachExistingOnly;
gatewayPort = inst.gatewayPort;
nixMode = inst.appDefaults.nixMode;
};
appInstall =
if !(pkgs.stdenv.hostPlatform.isDarwin && inst.app.install.enable && appPackage != null) then
null
else
{
name = lib.removePrefix "${homeDir}/" inst.app.install.path;
value = {
source = "${appPackage}/Applications/OpenClaw.app";
recursive = true;
force = true;
};
};
package = gatewayPackage;
in
{
homeFile = {
name = openclawLib.toRelative inst.configPath;
value = {
text = configJson;
force = true;
};
};
configFile = configFile;
configPath = inst.configPath;
codexRuntimeProfiles = codexRuntimeProfiles;
runtimeProfile = runtimeProfile;
dirs = [
inst.stateDir
inst.workspaceDir
(builtins.dirOf inst.logPath)
];
launchdAgent = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable) {
"${inst.launchd.label}" = {
enable = true;
config = {
Label = inst.launchd.label;
ProgramArguments = [
"${gatewayWrapper}/bin/openclaw-gateway-${name}"
"gateway"
"--port"
"${toString inst.gatewayPort}"
];
RunAtLoad = true;
KeepAlive = true;
WorkingDirectory = inst.stateDir;
StandardOutPath = inst.logPath;
StandardErrorPath = inst.logPath;
EnvironmentVariables = {
HOME = homeDir;
OPENCLAW_CONFIG_PATH = inst.configPath;
OPENCLAW_STATE_DIR = inst.stateDir;
OPENCLAW_IMAGE_BACKEND = "sips";
OPENCLAW_NIX_MODE = "1";
};
};
};
};
};
systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) {
"${inst.systemd.unitName}" = {
Unit = {
Description = "OpenClaw gateway (${name})";
};
Service = {
ExecStart = "${gatewayWrapper}/bin/openclaw-gateway-${name} gateway --port ${toString inst.gatewayPort}";
WorkingDirectory = inst.stateDir;
Restart = "always";
RestartSec = "1s";
Environment = [
"HOME=${homeDir}"
"OPENCLAW_CONFIG_PATH=${inst.configPath}"
"OPENCLAW_STATE_DIR=${inst.stateDir}"
"OPENCLAW_NIX_MODE=1"
];
StandardOutput = "append:${inst.logPath}";
StandardError = "append:${inst.logPath}";
systemdService = lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && inst.systemd.enable) {
"${inst.systemd.unitName}" = {
Unit = {
Description = "OpenClaw gateway (${name})";
};
Service = {
ExecStart = "${gatewayWrapper}/bin/openclaw-gateway-${name} gateway --port ${toString inst.gatewayPort}";
WorkingDirectory = inst.stateDir;
Restart = "always";
RestartSec = "1s";
Environment = [
"HOME=${homeDir}"
"OPENCLAW_CONFIG_PATH=${inst.configPath}"
"OPENCLAW_STATE_DIR=${inst.stateDir}"
"OPENCLAW_NIX_MODE=1"
];
StandardOutput = "append:${inst.logPath}";
StandardError = "append:${inst.logPath}";
};
};
};
};
appDefaults = appDefaults;
appInstall = appInstall;
package = package;
};
appDefaults = appDefaults;
appInstall = appInstall;
package = package;
launchdLabel =
if pkgs.stdenv.hostPlatform.isDarwin && inst.launchd.enable then inst.launchd.label else null;
};
instanceConfigs = lib.mapAttrsToList mkInstanceConfig enabledInstances;
codexRuntimeProfileEntries = lib.flatten (
map (
item:
map (profileDir: {
inherit profileDir;
binDir = "${item.runtimeProfile}/bin";
}) item.codexRuntimeProfiles
) instanceConfigs
);
codexRuntimeProfilesManifest = pkgs.writeText "openclaw-codex-runtime-profiles.tsv" (
(lib.concatStringsSep "\n" (
map (entry: "${entry.profileDir}\t${entry.binDir}") codexRuntimeProfileEntries
))
+ "\n"
);
appInstalls = lib.filter (item: item != null) (map (item: item.appInstall) instanceConfigs);
launchdLabels = lib.filter (label: label != null) (map (item: item.launchdLabel) instanceConfigs);
launchdLabelArgs = lib.concatStringsSep " " (map lib.escapeShellArg launchdLabels);
appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) {} instanceConfigs;
appDefaults = lib.foldl' (acc: item: lib.recursiveUpdate acc item.appDefaults) { } instanceConfigs;
appDefaultsEnabled = lib.filterAttrs (_: inst: inst.appDefaults.enable) enabledInstances;
in {
config = lib.mkIf (cfg.enable || cfg.instances != {}) {
in
{
config = lib.mkIf (cfg.enable || cfg.instances != { }) {
assertions = [
{
assertion = lib.length (lib.attrNames appDefaultsEnabled) <= 1;
message = "Only one OpenClaw instance may enable appDefaults.";
}
] ++ files.documentsAssertions ++ files.skillAssertions ++ plugins.pluginAssertions ++ plugins.pluginSkillAssertions;
]
++ files.documentsAssertions
++ files.duplicateSkillAssertion
++ plugins.pluginAssertions
++ [
{
assertion = !cfg.qmd.prewarmModels.enable || qmdPackage != null;
message = "programs.openclaw.qmd.prewarmModels.enable requires a qmd package in openclawPackages.";
}
];
home.packages = lib.unique (
(map (item: item.package) instanceConfigs)
@ -223,8 +347,6 @@ in {
};
})
(lib.listToAttrs appInstalls)
files.documentsFiles
files.skillFiles
plugins.pluginConfigFiles
(lib.optionalAttrs cfg.reloadScript.enable {
".local/bin/openclaw-reload" = {
@ -234,39 +356,72 @@ in {
})
];
home.activation.openclawDocumentGuard = lib.mkIf files.documentsEnabled (
lib.hm.dag.entryBefore [ "writeBoundary" ] ''
set -euo pipefail
${files.documentsGuard}
home.activation.openclawDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${
lib.concatStringsSep " " (lib.concatMap (item: item.dirs) instanceConfigs)
}
${lib.optionalString (plugins.pluginStateDirsAll != [ ])
"run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " plugins.pluginStateDirsAll}"
}
'';
home.activation.openclawWorkspaceFiles = lib.mkIf (files.materializedEntries != [ ]) (
lib.hm.dag.entryAfter [ "openclawDirs" ] ''
run --quiet ${../openclaw-materialize-workspace-files.sh} ${lib.escapeShellArg "${homeDir}/.local/state/nix-openclaw/managed-workspace-files"} ${files.materializedManifest}
''
);
home.activation.openclawDirs = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " (lib.concatMap (item: item.dirs) instanceConfigs)}
${lib.optionalString (plugins.pluginStateDirsAll != []) "run --quiet ${lib.getExe' pkgs.coreutils "mkdir"} -p ${lib.concatStringsSep " " plugins.pluginStateDirsAll}"}
home.activation.openclawConfigFiles = lib.hm.dag.entryAfter [ "openclawDirs" ] ''
${lib.concatStringsSep "\n" (
map (
item: "run --quiet ${lib.getExe' pkgs.coreutils "ln"} -sfn ${item.configFile} ${item.configPath}"
) instanceConfigs
)}
'';
home.activation.openclawConfigFiles = lib.hm.dag.entryAfter [ "openclawDirs" ] ''
${lib.concatStringsSep "\n" (map (item: "run --quiet ${lib.getExe' pkgs.coreutils "ln"} -sfn ${item.configFile} ${item.configPath}") instanceConfigs)}
'';
home.activation.openclawCodexRuntimeProfiles = lib.mkIf (codexRuntimeProfileEntries != [ ]) (
lib.hm.dag.entryAfter [ "openclawDirs" ] ''
run --quiet ${pkgs.bash}/bin/bash ${../openclaw-link-codex-runtime-profiles.sh} ${codexRuntimeProfilesManifest}
''
);
home.activation.openclawPluginGuard = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
set -euo pipefail
${plugins.pluginGuards}
'';
home.activation.openclawAppDefaults = lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != {}) (
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
# Nix mode + app defaults (OpenClaw.app)
/usr/bin/defaults write ai.openclaw.mac openclaw.nixMode -bool ${lib.boolToString (appDefaults.nixMode or true)}
/usr/bin/defaults write ai.openclaw.mac openclaw.gateway.attachExistingOnly -bool ${lib.boolToString (appDefaults.attachExistingOnly or true)}
/usr/bin/defaults write ai.openclaw.mac gatewayPort -int ${toString (appDefaults.gatewayPort or 18789)}
home.activation.openclawQmdPrewarm = lib.mkIf (cfg.qmd.prewarmModels.enable && qmdPackage != null) (
lib.hm.dag.entryAfter [ "openclawDirs" ] ''
run --quiet ${lib.getExe' pkgs.coreutils "env"} \
HOME=${lib.escapeShellArg homeDir} \
XDG_CACHE_HOME=${lib.escapeShellArg "${homeDir}/.cache"} \
XDG_CONFIG_HOME=${lib.escapeShellArg "${homeDir}/.config"} \
XDG_DATA_HOME=${lib.escapeShellArg "${homeDir}/.local/share"} \
OPENCLAW_QMD_BIN=${lib.escapeShellArg "${qmdPackage}/bin/qmd"} \
${pkgs.bash}/bin/bash ${../../../scripts/openclaw-qmd-prewarm.sh}
''
);
home.activation.openclawAppDefaults =
lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != { })
(
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
# Nix mode + app defaults (OpenClaw.app)
/usr/bin/defaults write ai.openclaw.mac openclaw.nixMode -bool ${
lib.boolToString (appDefaults.nixMode or true)
}
/usr/bin/defaults write ai.openclaw.mac openclaw.gateway.attachExistingOnly -bool ${
lib.boolToString (appDefaults.attachExistingOnly or true)
}
/usr/bin/defaults write ai.openclaw.mac gatewayPort -int ${
toString (appDefaults.gatewayPort or 18789)
}
''
);
home.activation.openclawLaunchdRelink = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin (
lib.hm.dag.entryAfter [ "linkGeneration" ] ''
/usr/bin/env bash ${../openclaw-launchd-relink.sh}
/usr/bin/env bash ${../openclaw-launchd-relink.sh} ${launchdLabelArgs}
''
);

View File

@ -1,8 +1,21 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
{
imports = [
(lib.mkRenamedOptionModule [ "programs" "openclaw" "firstParty" ] [ "programs" "openclaw" "bundledPlugins" ])
(lib.mkRenamedOptionModule [ "programs" "openclaw" "plugins" ] [ "programs" "openclaw" "customPlugins" ])
(lib.mkRemovedOptionModule [
"programs"
"openclaw"
"firstParty"
] "Use programs.openclaw.bundledPlugins.<name>.enable/config.")
(lib.mkRemovedOptionModule [
"programs"
"openclaw"
"plugins"
] "Use programs.openclaw.customPlugins.")
./options.nix
./config.nix
];

View File

@ -1,4 +1,10 @@
{ config, lib, pkgs, openclawLib, enabledInstances, plugins }:
{
lib,
pkgs,
openclawLib,
enabledInstances,
plugins,
}:
let
cfg = openclawLib.cfg;
@ -8,7 +14,8 @@ let
documentsEnabled = cfg.documents != null;
instanceWorkspaceDirs = map (inst: resolvePath inst.workspaceDir) (lib.attrValues enabledInstances);
renderSkill = skill:
renderSkill =
skill:
let
frontmatterLines = [
"---"
@ -24,65 +31,72 @@ let
frontmatter = lib.concatStringsSep "\n" frontmatterLines;
body = if skill ? body then skill.body else "";
in
"${frontmatter}\n\n${body}\n";
"${frontmatter}\n\n${body}\n";
skillAssertions =
duplicateSkillAssertion =
let
names = map (skill: skill.name) cfg.skills;
nameCounts = lib.foldl' (acc: name: acc // { "${name}" = (acc.${name} or 0) + 1; }) {} names;
duplicateNames = lib.attrNames (lib.filterAttrs (_: v: v > 1) nameCounts);
targetsForInstance =
instName: inst:
let
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
userTargets = map (skill: "${base}/${skill.name}") cfg.skills;
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ];
pluginTargets = lib.flatten (
map (p: map (skillPath: "${base}/${builtins.baseNameOf skillPath}") p.skills) pluginsForInstance
);
in
userTargets ++ pluginTargets;
skillTargets = lib.flatten (lib.mapAttrsToList targetsForInstance enabledInstances);
counts = lib.foldl' (acc: path: acc // { "${path}" = (acc.${path} or 0) + 1; }) { } skillTargets;
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
in
if duplicateNames == [] then [] else [
if duplicates == [ ] then
[ ]
else
[
{
assertion = false;
message = "programs.openclaw.skills has duplicate names: ${lib.concatStringsSep ", " duplicateNames}";
message = "Duplicate skill paths detected: ${lib.concatStringsSep ", " duplicates}";
}
];
skillFiles =
skillEntries =
let
entriesForInstance = instName: inst:
entriesForInstance =
instName: inst:
let
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
entryFor = skill:
entryFor =
skill:
let
mode = skill.mode or "symlink";
source = if skill ? source && skill.source != null then resolvePath skill.source else null;
in
if mode == "inline" then
{
name = "${base}/${skill.name}/SKILL.md";
value = { text = renderSkill skill; };
}
else if mode == "copy" then
{
name = "${base}/${skill.name}";
value = {
source = builtins.path {
name = "openclaw-skill-${skill.name}";
path = source;
};
recursive = true;
};
}
else
{
name = "${base}/${skill.name}";
value = {
source = config.lib.file.mkOutOfStoreSymlink source;
recursive = true;
};
if mode == "inline" then
{
source = pkgs.writeText "openclaw-skill-${skill.name}.md" (renderSkill skill);
target = "${resolvePath inst.workspaceDir}/skills/${skill.name}/SKILL.md";
}
else if mode == "copy" || mode == "symlink" then
{
source = builtins.path {
name = "openclaw-skill-${skill.name}";
path = source;
};
pluginEntriesFor = p:
target = "${resolvePath inst.workspaceDir}/skills/${skill.name}";
}
else
throw "Unsupported OpenClaw skill mode: ${mode}";
pluginEntriesFor =
p:
map (skillPath: {
name = "${base}/${builtins.baseNameOf skillPath}";
value = { source = skillPath; recursive = true; };
source = skillPath;
target = "${resolvePath inst.workspaceDir}/skills/${builtins.baseNameOf skillPath}";
}) p.skills;
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [];
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ];
in
(map entryFor cfg.skills) ++ (lib.flatten (map pluginEntriesFor pluginsForInstance));
(map entryFor cfg.skills) ++ (lib.flatten (map pluginEntriesFor pluginsForInstance));
in
lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances));
lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances);
documentsRequiredFiles = [
"AGENTS.md"
@ -103,9 +117,9 @@ let
let
extra = lib.filter (file: builtins.pathExists (cfg.documents + "/${file}")) documentsOptionalFiles;
in
documentsRequiredFiles ++ extra
documentsRequiredFiles ++ extra
else
[];
[ ];
documentsAssertions = lib.optionals documentsEnabled [
{
@ -126,77 +140,92 @@ let
}
];
documentsGuard =
lib.optionalString documentsEnabled (
let
guardLine = file: ''
if [ -e "${file}" ] && [ ! -L "${file}" ]; then
echo "OpenClaw documents are managed by Nix. Please adopt ${file} into your documents directory and re-run." >&2
exit 1
fi
'';
guardForDir = dir: ''
${lib.concatStringsSep "\n" (map (name: guardLine "${dir}/${name}") documentsFileNames)}
'';
in
lib.concatStringsSep "\n" (map guardForDir instanceWorkspaceDirs)
);
toolsReport =
if documentsEnabled then
let
toolNames = toolSets.toolNames or [];
renderPkgName = pkg:
if pkg ? pname then pkg.pname else lib.getName pkg;
renderPlugin = plugin:
let
pkgNames = map renderPkgName (lib.filter (p: p != null) plugin.packages);
pkgSuffix =
if pkgNames == []
then ""
else " " + (lib.concatStringsSep ", " pkgNames);
in
"- " + plugin.name + pkgSuffix + " (" + plugin.source + ")";
pluginLinesFor = instName: inst:
let
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [];
lines = if pluginsForInstance == [] then [ "- (none)" ] else map renderPlugin pluginsForInstance;
in
[
""
"### Instance: ${instName}"
] ++ lines;
reportLines =
renderPkgName = pkg: if pkg ? pname then pkg.pname else lib.getName pkg;
renderPkgCommand =
pkg:
let
pkgName = renderPkgName pkg;
commandName = pkg.meta.mainProgram or pkgName;
in
if commandName == pkgName then commandName else "${commandName} (${pkgName})";
toolPackages = lib.filter (p: p != null) (toolSets.tools or [ ]);
renderPlugin =
plugin:
let
pkgNames = map renderPkgCommand (lib.filter (p: p != null) plugin.packages);
pkgSuffix = if pkgNames == [ ] then "" else " " + (lib.concatStringsSep ", " pkgNames);
in
"- " + plugin.name + pkgSuffix + " (" + plugin.source + ")";
renderPkgList =
packages:
let
actualPackages = lib.filter (p: p != null) packages;
in
if actualPackages == [ ] then
[ "- (none)" ]
else
map (pkg: "- " + renderPkgCommand pkg) actualPackages;
pluginLinesFor =
instName: inst:
let
pluginsForInstance = plugins.resolvedPluginsByInstance.${instName} or [ ];
pluginLines =
if pluginsForInstance == [ ] then [ "- (none)" ] else map renderPlugin pluginsForInstance;
runtimePackages = lib.unique (
(lib.optional (openclawLib.qmdPackage != null) openclawLib.qmdPackage)
++ (cfg.runtimePackages or [ ])
++ (inst.runtimePackages or [ ])
);
in
[
"<!-- BEGIN NIX-REPORT -->"
""
"## Nix-managed tools"
""
"### Built-in toolchain"
"### Instance: ${instName}"
]
++ (if toolNames == [] then [ "- (none)" ] else map (name: "- " + name) toolNames)
++ [
""
"## Nix-managed plugin report"
""
"Plugins enabled per instance (last-wins on name collisions):"
"Plugins:"
]
++ lib.concatLists (lib.mapAttrsToList pluginLinesFor enabledInstances)
++ pluginLines
++ [
""
"Tools: batteries-included toolchain + plugin-provided CLIs."
""
"<!-- END NIX-REPORT -->"
];
"Runtime packages:"
]
++ renderPkgList runtimePackages;
reportLines = [
"<!-- BEGIN NIX-REPORT -->"
""
"## Nix-managed tools"
""
"### Built-in toolchain"
]
++ (
if toolPackages == [ ] then [ "- (none)" ] else map (pkg: "- " + renderPkgCommand pkg) toolPackages
)
++ [
""
"## Nix-managed plugin report"
""
"Plugins enabled per instance (last-wins on name collisions):"
]
++ lib.concatLists (lib.mapAttrsToList pluginLinesFor enabledInstances)
++ [
""
"Tools: batteries-included toolchain + runtime packages + plugin-provided CLIs."
""
"<!-- END NIX-REPORT -->"
];
reportText = lib.concatStringsSep "\n" reportLines;
in
pkgs.writeText "openclaw-tools-report.md" reportText
pkgs.writeText "openclaw-tools-report.md" reportText
else
null;
toolsWithReport =
if documentsEnabled then
pkgs.runCommand "openclaw-tools-with-report.md" {} ''
pkgs.runCommand "openclaw-tools-with-report.md" { } ''
cat ${cfg.documents + "/TOOLS.md"} > $out
echo "" >> $out
cat ${toolsReport} >> $out
@ -204,30 +233,39 @@ let
else
null;
documentsFiles =
documentEntries =
if documentsEnabled then
let
mkDocFiles = dir:
mkDocFiles =
dir:
let
mkDoc = name: {
name = toRelative (dir + "/${name}");
value = {
source = if name == "TOOLS.md" then toolsWithReport else cfg.documents + "/${name}";
};
source = if name == "TOOLS.md" then toolsWithReport else cfg.documents + "/${name}";
target = dir + "/${name}";
};
in
lib.listToAttrs (map mkDoc documentsFileNames);
map mkDoc documentsFileNames;
in
lib.mkMerge (map mkDocFiles instanceWorkspaceDirs)
lib.flatten (map mkDocFiles instanceWorkspaceDirs)
else
{};
[ ];
in {
materializedEntries = documentEntries ++ skillEntries;
materializedManifest =
let
renderEntry = entry: "${entry.source}\t${entry.target}";
in
pkgs.writeText "openclaw-workspace-files.tsv" (
(lib.concatStringsSep "\n" (map renderEntry materializedEntries)) + "\n"
);
in
{
inherit
documentsEnabled
documentsAssertions
documentsGuard
documentsFiles
skillAssertions
skillFiles;
materializedManifest
materializedEntries
duplicateSkillAssertion
;
}

View File

@ -1,4 +1,8 @@
{ config, lib, pkgs }:
{
config,
lib,
pkgs,
}:
let
cfg = config.programs.openclaw;
@ -9,59 +13,53 @@ let
toolNamesOverride = cfg.toolNames;
excludeToolNames = effectiveExcludeTools;
};
toolOverridesEnabled = cfg.toolNames != null || effectiveExcludeTools != [];
toolOverridesEnabled = cfg.toolNames != null || effectiveExcludeTools != [ ];
overlayPackage = pkgs.openclaw or null;
toolSets = import ../../../tools/extended.nix ({ inherit pkgs; } // toolOverrides);
defaultPackage =
if toolOverridesEnabled && cfg.package == pkgs.openclaw
then (pkgs.openclawPackages.withTools toolOverrides).openclaw
else cfg.package;
if toolOverridesEnabled && overlayPackage != null && cfg.package == overlayPackage then
(pkgs.openclawPackages.withTools toolOverrides).openclaw
else
cfg.package;
appPackage = if cfg.appPackage != null then cfg.appPackage else defaultPackage;
qmdPackage = pkgs.openclawPackages.qmd or null;
generatedConfigOptions = import ../../../generated/openclaw-config-options.nix { lib = lib; };
pluginCatalog = import ./plugin-catalog.nix;
bundledPluginSources = let
stepieteRev = "983210e3b6e9285780e87f48ce9354b51a270e95";
stepieteNarHash = "sha256-fY8t41kMSHu2ovf89mIdvC7vkceroCwKxw/MKVn4rsE=";
stepiete = tool:
"github:openclaw/nix-steipete-tools?dir=tools/${tool}&rev=${stepieteRev}&narHash=${stepieteNarHash}";
in {
summarize = stepiete "summarize";
peekaboo = stepiete "peekaboo";
oracle = stepiete "oracle";
poltergeist = stepiete "poltergeist";
sag = stepiete "sag";
camsnap = stepiete "camsnap";
gogcli = stepiete "gogcli";
goplaces = stepiete "goplaces";
bird = stepiete "bird";
sonoscli = stepiete "sonoscli";
imsg = stepiete "imsg";
};
bundledPlugins = lib.filter (p: p != null) (lib.mapAttrsToList (name: source:
bundledPluginSources =
let
pluginCfg = cfg.bundledPlugins.${name};
openclawToolsRev = "4c1cee3c7eaf68f9de0f756be1484534f5bb5f34";
openclawToolsNarHash = "sha256-tXWkN1VnwFG8XlRqW/e7VwbKnUfyU9tB7YDm9QHJXTY=";
openclawTools =
tool:
"github:openclaw/nix-openclaw-tools?dir=tools/${tool}&rev=${openclawToolsRev}&narHash=${openclawToolsNarHash}";
in
if (pluginCfg.enable or false) then {
inherit source;
config = pluginCfg.config or {};
} else null
) bundledPluginSources);
lib.mapAttrs (_name: plugin: plugin.source or (openclawTools plugin.tool)) pluginCatalog;
bundledPlugins = lib.filter (p: p != null) (
lib.mapAttrsToList (
name: source:
let
pluginCfg = cfg.bundledPlugins.${name};
in
if (pluginCfg.enable or false) then
{
inherit source;
config = pluginCfg.config or { };
}
else
null
) bundledPluginSources
);
effectivePlugins = cfg.customPlugins ++ bundledPlugins;
resolvePath = p:
if lib.hasPrefix "~/" p then
"${homeDir}/${lib.removePrefix "~/" p}"
else
p;
resolvePath = p: if lib.hasPrefix "~/" p then "${homeDir}/${lib.removePrefix "~/" p}" else p;
toRelative = p:
if lib.hasPrefix "${homeDir}/" p then
lib.removePrefix "${homeDir}/" p
else
p;
toRelative = p: if lib.hasPrefix "${homeDir}/" p then lib.removePrefix "${homeDir}/" p else p;
in {
in
{
inherit
cfg
homeDir
@ -70,10 +68,12 @@ in {
toolSets
defaultPackage
appPackage
qmdPackage
generatedConfigOptions
bundledPluginSources
bundledPlugins
effectivePlugins
resolvePath
toRelative;
toRelative
;
}

View File

@ -1,4 +1,8 @@
{ lib, openclawLib }:
{
lib,
openclawLib,
pluginOptionType,
}:
{ name, config, ... }:
{
@ -17,9 +21,11 @@
stateDir = lib.mkOption {
type = lib.types.str;
default = if name == "default"
then "${openclawLib.homeDir}/.openclaw"
else "${openclawLib.homeDir}/.openclaw-${name}";
default =
if name == "default" then
"${openclawLib.homeDir}/.openclaw"
else
"${openclawLib.homeDir}/.openclaw-${name}";
description = "State directory for this OpenClaw instance (logs, sessions, config).";
};
@ -37,9 +43,11 @@
logPath = lib.mkOption {
type = lib.types.str;
default = if name == "default"
then "/tmp/openclaw/openclaw-gateway.log"
else "/tmp/openclaw/openclaw-gateway-${name}.log";
default =
if name == "default" then
"/tmp/openclaw/openclaw-gateway.log"
else
"/tmp/openclaw/openclaw-gateway-${name}.log";
description = "Log path for this OpenClaw gateway instance.";
};
@ -61,27 +69,27 @@
description = "pnpmDeps hash for local gateway builds (omit to let Nix suggest the correct hash).";
};
runtimePackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = "Extra packages visible to this OpenClaw instance and its isolated Codex harness only. These are not added to the user's PATH.";
};
environment = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Extra runtime environment for this OpenClaw gateway wrapper. Values that point to files are read at runtime unless the variable name ends in _FILE.";
};
plugins = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.str;
description = "Plugin source pointer (e.g., github:owner/repo or path:/...).";
};
config = lib.mkOption {
type = lib.types.attrs;
default = {};
description = "Plugin-specific configuration (env/files/etc).";
};
};
});
type = lib.types.listOf pluginOptionType;
default = openclawLib.effectivePlugins;
description = "Plugins enabled for this instance (includes first-party toggles).";
description = "Plugins enabled for this instance (includes bundled plugin toggles).";
};
config = lib.mkOption {
type = lib.types.submodule { options = openclawLib.generatedConfigOptions; };
default = {};
default = { };
description = "OpenClaw config (schema-typed).";
};
@ -93,9 +101,11 @@
launchd.label = lib.mkOption {
type = lib.types.str;
default = if name == "default"
then "com.steipete.openclaw.gateway"
else "com.steipete.openclaw.gateway.${name}";
default =
if name == "default" then
"com.steipete.openclaw.gateway"
else
"com.steipete.openclaw.gateway.${name}";
description = "launchd label for this instance.";
};
@ -107,9 +117,7 @@
systemd.unitName = lib.mkOption {
type = lib.types.str;
default = if name == "default"
then "openclaw-gateway"
else "openclaw-gateway-${name}";
default = if name == "default" then "openclaw-gateway" else "openclaw-gateway-${name}";
description = "systemd user service unit name for this instance.";
};

View File

@ -1,8 +1,42 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
openclawLib = import ./lib.nix { inherit config lib pkgs; };
instanceModule = import ./options-instance.nix { inherit lib openclawLib; };
pluginOptionType = lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.str;
description = "Plugin source. Use a plugin flake source (github:/path:) or an OpenClaw npm install source (npm:@scope/package@version).";
};
config = lib.mkOption {
type = lib.types.attrs;
default = { };
description = "Nix capability plugin configuration (env/files/etc). Runtime OpenClaw plugin config belongs under programs.openclaw.config.plugins.entries.<id>.config.";
};
id = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "OpenClaw runtime plugin id. Required for npm: sources so Nix can enable the plugin without build-time introspection.";
};
enabled = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Default enabled state for an OpenClaw runtime plugin entry.";
};
hash = lib.mkOption {
type = lib.types.str;
default = lib.fakeHash;
description = "Recursive output hash for npm: runtime plugin sources. Use the hash Nix reports when this is left as lib.fakeHash.";
};
};
};
instanceModule = import ./options-instance.nix { inherit lib openclawLib pluginOptionType; };
pluginCatalog = import ./plugin-catalog.nix;
mkSkillOption = lib.types.submodule {
options = {
name = lib.mkOption {
@ -30,7 +64,11 @@ let
description = "Optional openclaw metadata for the skill frontmatter.";
};
mode = lib.mkOption {
type = lib.types.enum [ "symlink" "copy" "inline" ];
type = lib.types.enum [
"symlink"
"copy"
"inline"
];
default = "symlink";
description = "Install mode for the skill (symlink/copy/inline).";
};
@ -42,7 +80,8 @@ let
};
};
in {
in
{
options.programs.openclaw = {
enable = lib.mkEnableOption "OpenClaw (batteries-included)";
@ -60,7 +99,7 @@ in {
excludeTools = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [];
default = [ ];
description = "Tool names to remove from the built-in toolchain.";
};
@ -96,6 +135,18 @@ in {
};
};
runtimePackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
description = "Extra packages visible to the OpenClaw gateway and isolated Codex harness only. These are not added to the user's PATH.";
};
environment = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Extra runtime environment for OpenClaw gateway wrappers. Values that point to files are read at runtime unless the variable name ends in _FILE.";
};
documents = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
@ -104,54 +155,28 @@ in {
skills = lib.mkOption {
type = lib.types.listOf mkSkillOption;
default = [];
default = [ ];
description = "Declarative skills installed into each instance workspace.";
};
customPlugins = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.str;
description = "Plugin source pointer (e.g., github:owner/repo or path:/...).";
};
config = lib.mkOption {
type = lib.types.attrs;
default = {};
description = "Plugin-specific configuration (env/files/etc).";
};
};
});
default = [];
description = "Custom/community plugins (merged with bundled plugin toggles).";
type = lib.types.listOf pluginOptionType;
default = [ ];
description = "Custom/community plugins (merged with bundled plugin toggles). Flake sources provide Nix capability plugins; npm: sources provide OpenClaw runtime plugins.";
};
bundledPlugins = let
mkPlugin = { name, defaultEnable ? false }: {
enable = lib.mkOption {
type = lib.types.bool;
default = defaultEnable;
description = "Enable the ${name} plugin (bundled).";
};
config = lib.mkOption {
type = lib.types.attrs;
default = {};
description = "Bundled plugin configuration passed through to ${name} (env/settings).";
};
bundledPlugins = lib.mapAttrs (name: plugin: {
enable = lib.mkOption {
type = lib.types.bool;
default = plugin.defaultEnable or false;
description = "Enable the ${name} plugin (bundled).";
};
in {
summarize = mkPlugin { name = "summarize"; };
peekaboo = mkPlugin { name = "peekaboo"; };
oracle = mkPlugin { name = "oracle"; };
poltergeist = mkPlugin { name = "poltergeist"; };
sag = mkPlugin { name = "sag"; };
camsnap = mkPlugin { name = "camsnap"; };
gogcli = mkPlugin { name = "gogcli"; };
goplaces = mkPlugin { name = "goplaces"; defaultEnable = true; };
bird = mkPlugin { name = "bird"; };
sonoscli = mkPlugin { name = "sonoscli"; };
imsg = mkPlugin { name = "imsg"; };
};
config = lib.mkOption {
type = lib.types.attrs;
default = { };
description = "Bundled plugin configuration passed through to ${name} (env/settings).";
};
}) pluginCatalog;
launchd.enable = lib.mkOption {
type = lib.types.bool;
@ -179,16 +204,22 @@ in {
instances = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule instanceModule);
default = {};
default = { };
description = "Named OpenClaw instances (prod/test).";
};
exposePluginPackages = lib.mkOption {
type = lib.types.bool;
default = true;
default = false;
description = "Add plugin packages to home.packages so CLIs are on PATH.";
};
qmd.prewarmModels.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Download/check QMD's default GGUF models during Home Manager activation. This uses about 2.25GB under the user's QMD cache.";
};
reloadScript = {
enable = lib.mkOption {
type = lib.types.bool;
@ -199,7 +230,7 @@ in {
config = lib.mkOption {
type = lib.types.submodule { options = openclawLib.generatedConfigOptions; };
default = {};
default = { };
description = "OpenClaw config (schema-typed).";
};
};

View File

@ -0,0 +1,74 @@
{
summarize = {
tool = "summarize";
description = "Summarize URLs, PDFs, YouTube videos";
linux = true;
};
discrawl = {
tool = "discrawl";
description = "Archive and search Discord history";
linux = true;
};
wacrawl = {
tool = "wacrawl";
description = "Archive and search WhatsApp Desktop history";
linux = true;
};
peekaboo = {
tool = "peekaboo";
description = "Screenshot your screen";
linux = false;
};
poltergeist = {
tool = "poltergeist";
description = "File watching and automation";
linux = false;
};
sag = {
tool = "sag";
description = "Text-to-speech";
linux = true;
};
camsnap = {
tool = "camsnap";
description = "Take photos from connected cameras";
linux = true;
};
gogcli = {
tool = "gogcli";
description = "Google Calendar integration";
linux = true;
};
goplaces = {
tool = "goplaces";
description = "Google Places API (New) CLI";
defaultEnable = true;
linux = true;
};
qmd = {
tool = "qmd";
description = "Search local markdown knowledge bases";
linux = true;
};
sonoscli = {
tool = "sonoscli";
description = "Control Sonos speakers";
linux = true;
};
imsg = {
tool = "imsg";
description = "Send/read iMessages";
linux = false;
};
}

View File

@ -1,185 +1,280 @@
{ lib, pkgs, openclawLib, enabledInstances }:
{
lib,
pkgs,
openclawLib,
enabledInstances,
}:
let
resolvePath = openclawLib.resolvePath;
toRelative = openclawLib.toRelative;
mkNpmRuntimePlugin = pkgs.callPackage ../../../lib/npm-runtime-plugin.nix { };
resolvePlugin = plugin: let
flake = builtins.getFlake plugin.source;
system = pkgs.stdenv.hostPlatform.system;
openclawPluginRaw =
if flake ? openclawPlugin then flake.openclawPlugin
else throw "openclawPlugin missing in ${plugin.source}";
openclawPlugin =
if builtins.isFunction openclawPluginRaw
then openclawPluginRaw system
else openclawPluginRaw;
resolvedPlugin =
if openclawPlugin == null
then throw "openclawPlugin is null in ${plugin.source} for ${system}"
else openclawPlugin;
needs = resolvedPlugin.needs or {};
in {
source = plugin.source;
name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}");
skills = resolvedPlugin.skills or [];
packages = resolvedPlugin.packages or [];
needs = {
stateDirs = needs.stateDirs or [];
requiredEnv = needs.requiredEnv or [];
};
config = plugin.config or {};
};
resolvedPluginsByInstance =
lib.mapAttrs (instName: inst:
let
resolved = map resolvePlugin inst.plugins;
counts = lib.foldl' (acc: p:
acc // { "${p.name}" = (acc.${p.name} or 0) + 1; }
) {} resolved;
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
byName = lib.foldl' (acc: p: acc // { "${p.name}" = p; }) {} resolved;
ordered = lib.attrValues byName;
in
if duplicates == []
then ordered
else lib.warn
"programs.openclaw.instances.${instName}: duplicate plugin names detected (${lib.concatStringsSep ", " duplicates}); last entry wins."
ordered
) enabledInstances;
pluginPackagesFor = instName:
lib.flatten (map (p: p.packages) (resolvedPluginsByInstance.${instName} or []));
pluginPackagesAll =
lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances));
pluginStateDirsFor = instName:
normalizeOpenClawPlugin =
pluginSource: name: entry:
let
dirs = lib.flatten (map (p: p.needs.stateDirs) (resolvedPluginsByInstance.${instName} or []));
id = entry.id or (throw "openclawPlugin ${name}: plugins entry missing id");
path = entry.path or (throw "openclawPlugin ${name}: plugins.${id} missing path");
enabled =
if entry ? enable && !(entry ? enabled) then
throw "openclawPlugin ${name}: plugins.${id}.enable is not supported; use enabled"
else if entry ? enabled then
if builtins.isBool entry.enabled then
entry.enabled
else
throw "openclawPlugin ${name}: plugins.${id}.enabled must be a boolean"
else
true;
in
map (dir: resolvePath ("~/" + dir)) dirs;
{
inherit id path enabled;
source = pluginSource;
plugin = name;
};
pluginStateDirsAll =
lib.flatten (map pluginStateDirsFor (lib.attrNames enabledInstances));
pluginEnvFor = instName:
resolveNpmRuntimePlugin =
plugin:
let
entries = resolvedPluginsByInstance.${instName} or [];
toPairs = p:
id = plugin.id or (throw "OpenClaw npm runtime plugin ${plugin.source} requires id");
path = mkNpmRuntimePlugin {
inherit id;
source = plugin.source;
hash = plugin.hash or lib.fakeHash;
};
in
if (plugin.config or { }) != { } then
throw "OpenClaw npm runtime plugin ${plugin.source} must put runtime config under programs.openclaw.config.plugins.entries.${id}.config, not customPlugins.config"
else
{
source = plugin.source;
name = id;
skills = [ ];
packages = [ ];
plugins = [
{
inherit id path;
enabled = plugin.enabled or true;
source = plugin.source;
plugin = id;
}
];
needs = {
stateDirs = [ ];
requiredEnv = [ ];
};
config = { };
};
resolveFlakePlugin =
plugin:
let
_ =
if (plugin.id or null) != null then
throw "Plugin ${plugin.source}: id is only valid for npm: OpenClaw runtime plugin sources"
else if (plugin.hash or lib.fakeHash) != lib.fakeHash then
throw "Plugin ${plugin.source}: hash is only valid for npm: OpenClaw runtime plugin sources"
else if (plugin.enabled or true) != true then
throw "Plugin ${plugin.source}: enabled is only valid for npm: OpenClaw runtime plugin sources"
else
null;
system = pkgs.stdenv.hostPlatform.system;
flake = builtins.getFlake plugin.source;
openclawPluginRaw =
if flake ? openclawPlugin then
flake.openclawPlugin
else
throw "openclawPlugin missing in ${plugin.source}";
openclawPlugin =
if builtins.isFunction openclawPluginRaw then openclawPluginRaw system else openclawPluginRaw;
resolvedPlugin =
if openclawPlugin == null then
throw "openclawPlugin is null in ${plugin.source} for ${system}"
else
openclawPlugin;
name = resolvedPlugin.name or (throw "openclawPlugin.name missing in ${plugin.source}");
needs = resolvedPlugin.needs or { };
in
builtins.seq _ {
source = plugin.source;
inherit name;
skills = resolvedPlugin.skills or [ ];
packages = resolvedPlugin.packages or [ ];
plugins = map (normalizeOpenClawPlugin plugin.source name) (resolvedPlugin.plugins or [ ]);
needs = {
stateDirs = needs.stateDirs or [ ];
requiredEnv = needs.requiredEnv or [ ];
};
config = plugin.config or { };
};
resolvePlugin =
plugin:
if lib.hasPrefix "npm:" plugin.source then
resolveNpmRuntimePlugin plugin
else
resolveFlakePlugin plugin;
resolvedPluginsByInstance = lib.mapAttrs (
instName: inst:
let
resolved = map resolvePlugin inst.plugins;
counts = lib.foldl' (acc: p: acc // { "${p.name}" = (acc.${p.name} or 0) + 1; }) { } resolved;
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
byName = lib.foldl' (acc: p: acc // { "${p.name}" = p; }) { } resolved;
ordered = lib.attrValues byName;
in
if duplicates == [ ] then
ordered
else
lib.warn "programs.openclaw.instances.${instName}: duplicate plugin names detected (${lib.concatStringsSep ", " duplicates}); last entry wins." ordered
) enabledInstances;
pluginPackagesFor =
instName: lib.flatten (map (p: p.packages) (resolvedPluginsByInstance.${instName} or [ ]));
pluginPackagesAll = lib.flatten (map pluginPackagesFor (lib.attrNames enabledInstances));
pluginStateDirsFor =
instName:
let
dirs = lib.flatten (map (p: p.needs.stateDirs) (resolvedPluginsByInstance.${instName} or [ ]));
in
map (dir: resolvePath ("~/" + dir)) dirs;
pluginStateDirsAll = lib.flatten (map pluginStateDirsFor (lib.attrNames enabledInstances));
pluginEnvFor =
instName:
let
entries = resolvedPluginsByInstance.${instName} or [ ];
toPairs =
p:
let
env = (p.config.env or {});
env = (p.config.env or { });
required = p.needs.requiredEnv;
in
map (k: { key = k; value = env.${k} or ""; plugin = p.name; }) required;
map (k: {
key = k;
value = env.${k} or "";
plugin = p.name;
}) required;
in
lib.flatten (map toPairs entries);
lib.flatten (map toPairs entries);
pluginEnvAllFor = instName:
pluginEnvAllFor =
instName:
let
entries = resolvedPluginsByInstance.${instName} or [];
toPairs = p:
let env = (p.config.env or {});
in map (k: { key = k; value = env.${k}; plugin = p.name; }) (lib.attrNames env);
entries = resolvedPluginsByInstance.${instName} or [ ];
toPairs =
p:
let
env = (p.config.env or { });
in
map (k: {
key = k;
value = env.${k};
plugin = p.name;
}) (lib.attrNames env);
in
lib.flatten (map toPairs entries);
lib.flatten (map toPairs entries);
openclawPluginsFor =
instName: lib.flatten (map (p: p.plugins) (resolvedPluginsByInstance.${instName} or [ ]));
openclawPluginLoadPathsFor = instName: map (p: toString p.path) (openclawPluginsFor instName);
openclawPluginEntriesConfigFor =
instName:
let
entries = openclawPluginsFor instName;
in
lib.optionalAttrs (entries != [ ]) {
plugins = {
entries = lib.listToAttrs (
map (p: {
name = p.id;
value = {
enabled = p.enabled;
};
}) entries
);
};
};
openclawPluginIdAssertions = lib.mapAttrsToList (
instName: _inst:
let
ids = map (p: p.id) (openclawPluginsFor instName);
counts = lib.foldl' (acc: id: acc // { "${id}" = (acc.${id} or 0) + 1; }) { } ids;
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
in
{
assertion = duplicates == [ ];
message = "programs.openclaw.instances.${instName}: duplicate OpenClaw plugin ids detected: ${lib.concatStringsSep ", " duplicates}";
}
) enabledInstances;
pluginAssertions =
lib.flatten (lib.mapAttrsToList (instName: inst:
let
plugins = resolvedPluginsByInstance.${instName} or [];
envFor = p: (p.config.env or {});
missingFor = p:
lib.filter (req: !(builtins.hasAttr req (envFor p))) p.needs.requiredEnv;
configMissingStateDir = p:
(p.config.settings or {}) != {} && (p.needs.stateDirs or []) == [];
mkAssertion = p:
let
missing = missingFor p;
in {
assertion = missing == [];
message = "programs.openclaw.instances.${instName}: plugin ${p.name} missing required env: ${lib.concatStringsSep ", " missing}";
};
mkConfigAssertion = p: {
assertion = !(configMissingStateDir p);
message = "programs.openclaw.instances.${instName}: plugin ${p.name} provides settings but declares no stateDirs (needed for config.json).";
};
in
(map mkAssertion plugins) ++ (map mkConfigAssertion plugins)
) enabledInstances);
pluginSkillsFiles =
let
entriesForInstance = instName: inst:
openclawPluginIdAssertions
++ lib.flatten (
lib.mapAttrsToList (
instName: inst:
let
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
skillEntriesFor = p:
map (skillPath: {
name = "${base}/${builtins.baseNameOf skillPath}";
value = { source = skillPath; recursive = true; };
}) p.skills;
plugins = resolvedPluginsByInstance.${instName} or [];
plugins = resolvedPluginsByInstance.${instName} or [ ];
envFor = p: (p.config.env or { });
missingFor = p: lib.filter (req: !(builtins.hasAttr req (envFor p))) p.needs.requiredEnv;
configMissingStateDir = p: (p.config.settings or { }) != { } && (p.needs.stateDirs or [ ]) == [ ];
mkAssertion =
p:
let
missing = missingFor p;
in
{
assertion = missing == [ ];
message = "programs.openclaw.instances.${instName}: plugin ${p.name} missing required env: ${lib.concatStringsSep ", " missing}";
};
mkConfigAssertion = p: {
assertion = !(configMissingStateDir p);
message = "programs.openclaw.instances.${instName}: plugin ${p.name} provides settings but declares no stateDirs (needed for config.json).";
};
in
lib.flatten (map skillEntriesFor plugins);
in
lib.listToAttrs (lib.flatten (lib.mapAttrsToList entriesForInstance enabledInstances));
(map mkAssertion plugins) ++ (map mkConfigAssertion plugins)
) enabledInstances
);
pluginConfigFiles =
let
entryFor = instName: inst:
entryFor =
instName: inst:
let
plugins = resolvedPluginsByInstance.${instName} or [];
mkEntries = p:
plugins = resolvedPluginsByInstance.${instName} or [ ];
mkEntries =
p:
let
cfg = p.config.settings or {};
dir =
if (p.needs.stateDirs or []) == []
then null
else lib.head (p.needs.stateDirs or []);
cfg = p.config.settings or { };
dir = if (p.needs.stateDirs or [ ]) == [ ] then null else lib.head (p.needs.stateDirs or [ ]);
in
if cfg == {} then
[]
else
(if dir == null then
if cfg == { } then
[ ]
else
(
if dir == null then
throw "plugin ${p.name} provides settings but no stateDirs are defined"
else [
{
name = toRelative (resolvePath ("~/" + dir + "/config.json"));
value = { text = builtins.toJSON cfg; };
}
]);
else
[
{
name = toRelative (resolvePath ("~/" + dir + "/config.json"));
value = {
text = builtins.toJSON cfg;
};
}
]
);
in
lib.flatten (map mkEntries plugins);
lib.flatten (map mkEntries plugins);
entries = lib.flatten (lib.mapAttrsToList entryFor enabledInstances);
in
lib.listToAttrs entries;
pluginSkillAssertions =
let
skillTargets =
lib.flatten (lib.concatLists (lib.mapAttrsToList (instName: inst:
let
base = "${toRelative (resolvePath inst.workspaceDir)}/skills";
plugins = resolvedPluginsByInstance.${instName} or [];
in
map (p:
map (skillPath:
"${base}/${p.name}/${builtins.baseNameOf skillPath}"
) p.skills
) plugins
) enabledInstances));
counts = lib.foldl' (acc: path:
acc // { "${path}" = (acc.${path} or 0) + 1; }
) {} skillTargets;
duplicates = lib.attrNames (lib.filterAttrs (_: v: v > 1) counts);
in
if duplicates == [] then [] else [
{
assertion = false;
message = "Duplicate skill paths detected: ${lib.concatStringsSep ", " duplicates}";
}
];
lib.listToAttrs entries;
pluginGuards =
let
@ -193,13 +288,14 @@ let
exit 1
fi
'';
entriesForInstance = instName:
map (entry: entry // { instance = instName; }) (pluginEnvFor instName);
entriesForInstance =
instName: map (entry: entry // { instance = instName; }) (pluginEnvFor instName);
entries = lib.flatten (map entriesForInstance (lib.attrNames enabledInstances));
in
lib.concatStringsSep "\n" (map renderCheck entries);
lib.concatStringsSep "\n" (map renderCheck entries);
in {
in
{
inherit
resolvedPluginsByInstance
pluginPackagesFor
@ -208,9 +304,11 @@ in {
pluginStateDirsAll
pluginEnvFor
pluginEnvAllFor
openclawPluginsFor
openclawPluginLoadPathsFor
openclawPluginEntriesConfigFor
pluginAssertions
pluginSkillsFiles
pluginConfigFiles
pluginSkillAssertions
pluginGuards;
pluginGuards
;
}

View File

@ -0,0 +1,217 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.openclaw-gateway;
deepConfigType = lib.types.mkOptionType {
name = "openclaw-config-attrs";
description = "OpenClaw JSON config (attrset), merged deeply via lib.recursiveUpdate.";
check = builtins.isAttrs;
merge = _loc: defs: lib.foldl' lib.recursiveUpdate { } (map (d: d.value) defs);
};
configJson = builtins.toJSON cfg.config;
generatedConfigFile = pkgs.writeText "openclaw.json" configJson;
configFile = if cfg.configFile != null then cfg.configFile else generatedConfigFile;
# `environment.etc` takes a relative path.
etcRelPath = lib.removePrefix "/etc/" cfg.configPath;
execStartCmd =
if cfg.execStart != null then
cfg.execStart
else
"${cfg.package}/bin/openclaw gateway --port ${toString cfg.port}";
in
{
options.services.openclaw-gateway = with lib; {
enable = mkEnableOption "OpenClaw gateway (openclaw gateway as a systemd service)";
unitName = mkOption {
type = types.str;
default = "openclaw-gateway";
description = "systemd unit name (service will be <unitName>.service).";
};
package = mkOption {
type = types.package;
default = if pkgs ? openclaw then pkgs.openclaw else pkgs.openclaw-gateway;
description = "OpenClaw gateway package.";
};
port = mkOption {
type = types.port;
default = 18789;
description = "Gateway listen port.";
};
user = mkOption {
type = types.str;
default = "openclaw";
description = "System user running the gateway.";
};
group = mkOption {
type = types.str;
default = "openclaw";
description = "System group running the gateway.";
};
createUser = mkOption {
type = types.bool;
default = true;
description = "Create the user/group automatically.";
};
stateDir = mkOption {
type = types.str;
default = "/var/lib/openclaw";
description = "State dir (OPENCLAW_STATE_DIR).";
};
workingDirectory = mkOption {
type = types.str;
default = cfg.stateDir;
description = "Working directory for the systemd service.";
};
configPath = mkOption {
type = types.str;
default = "/etc/openclaw/openclaw.json";
description = "Path to the OpenClaw JSON config file (OPENCLAW_CONFIG_PATH). Must be under /etc.";
};
configFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Optional path to an existing config file. If set, it is copied to configPath (under /etc).";
};
config = mkOption {
type = deepConfigType;
default = { };
description = "OpenClaw JSON config (attrset), deep-merged across definitions.";
};
logPath = mkOption {
type = types.str;
default = "${cfg.stateDir}/logs/gateway.log";
description = "Log file path (systemd StandardOutput/StandardError append).";
};
environment = mkOption {
type = types.attrsOf types.str;
default = { };
description = "Additional environment variables for the gateway process.";
};
environmentFiles = mkOption {
type = types.listOf types.str;
default = [ ];
description = "systemd EnvironmentFile= entries (use leading '-' to ignore missing).";
};
execStart = mkOption {
type = types.nullOr types.str;
default = null;
description = "Override ExecStart command. If unset, runs: openclaw gateway --port <port>.";
};
execStartPre = mkOption {
type = types.listOf types.str;
default = [ ];
description = "List of ExecStartPre= commands.";
};
servicePath = mkOption {
type = types.listOf types.package;
default = [ ];
description = "Extra packages added to systemd service PATH.";
};
restart = mkOption {
type = types.str;
default = "always";
description = "systemd Restart=.";
};
restartSec = mkOption {
type = types.int;
default = 2;
description = "systemd RestartSec=.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = lib.hasPrefix "/etc/" cfg.configPath;
message = "services.openclaw-gateway.configPath must be under /etc (got: ${cfg.configPath}).";
}
];
users.groups.${cfg.group} = lib.mkIf cfg.createUser { };
users.users.${cfg.user} = lib.mkIf cfg.createUser {
isSystemUser = true;
group = cfg.group;
home = cfg.stateDir;
createHome = true;
shell = pkgs.bashInteractive;
};
systemd.tmpfiles.rules = [
"d ${cfg.stateDir} 0750 ${cfg.user} ${cfg.group} - -"
"d ${builtins.dirOf cfg.logPath} 0750 ${cfg.user} ${cfg.group} - -"
"d ${builtins.dirOf cfg.configPath} 0755 root root - -"
];
environment.etc.${etcRelPath} = {
mode = "0644";
source = configFile;
};
systemd.services.${cfg.unitName} = {
description = "OpenClaw gateway";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = {
OPENCLAW_CONFIG_PATH = cfg.configPath;
OPENCLAW_STATE_DIR = cfg.stateDir;
# Backward-compatible env names.
CLAWDBOT_CONFIG_PATH = cfg.configPath;
CLAWDBOT_STATE_DIR = cfg.stateDir;
}
// cfg.environment;
serviceConfig = {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.workingDirectory;
EnvironmentFile = cfg.environmentFiles;
ExecStartPre = cfg.execStartPre;
ExecStart = execStartCmd;
Restart = cfg.restart;
RestartSec = cfg.restartSec;
StandardOutput = "append:${cfg.logPath}";
StandardError = "append:${cfg.logPath}";
};
path = [
pkgs.bash
pkgs.coreutils
]
++ cfg.servicePath;
};
};
}

View File

@ -1,14 +1,38 @@
{
openclawToolPkgs ? { },
qmdPkgs ? { },
}:
final: prev:
let
packages = import ./packages { pkgs = prev; };
toolNames = (import ./tools/extended.nix { pkgs = prev; }).toolNames;
withTools = { toolNamesOverride ? null, excludeToolNames ? [] }:
qmdPackage =
if prev.stdenv.hostPlatform.isDarwin then
openclawToolPkgs.qmd or null
else
qmdPkgs.qmd or qmdPkgs.default or null;
packages = import ./packages {
pkgs = prev;
openclawToolPkgs = openclawToolPkgs;
inherit qmdPackage;
};
toolNames =
(import ./tools/extended.nix {
pkgs = prev;
openclawToolPkgs = openclawToolPkgs;
}).toolNames;
withTools =
{
toolNamesOverride ? null,
excludeToolNames ? [ ],
}:
import ./packages {
pkgs = prev;
openclawToolPkgs = openclawToolPkgs;
inherit qmdPackage;
inherit toolNamesOverride excludeToolNames;
};
in
packages // {
packages
// {
openclawPackages = packages // {
inherit toolNames withTools;
};

View File

@ -1,14 +1,16 @@
{ pkgs
, sourceInfo ? import ../sources/openclaw-source.nix
, steipetePkgs ? {}
, toolNamesOverride ? null
, excludeToolNames ? []
{
pkgs,
sourceInfo ? import ../sources/openclaw-source.nix,
openclawToolPkgs ? { },
qmdPackage ? null,
toolNamesOverride ? null,
excludeToolNames ? [ ],
}:
let
isDarwin = pkgs.stdenv.hostPlatform.isDarwin;
toolSets = import ../tools/extended.nix {
pkgs = pkgs;
steipetePkgs = steipetePkgs;
openclawToolPkgs = openclawToolPkgs;
inherit toolNamesOverride excludeToolNames;
};
openclawGateway = pkgs.callPackage ./openclaw-gateway.nix {
@ -16,18 +18,17 @@ let
pnpmDepsHash = sourceInfo.pnpmDepsHash or null;
};
openclawApp = if isDarwin then pkgs.callPackage ./openclaw-app.nix { } else null;
openclawTools = pkgs.buildEnv {
name = "openclaw-tools";
paths = toolSets.tools;
pathsToLink = [ "/bin" ];
};
openclawBundle = pkgs.callPackage ./openclaw-batteries.nix {
openclaw-gateway = openclawGateway;
openclaw-app = openclawApp;
extendedTools = toolSets.tools;
inherit qmdPackage;
version = sourceInfo.releaseVersion or null;
};
in {
in
{
openclaw-gateway = openclawGateway;
openclaw = openclawBundle;
openclaw-tools = openclawTools;
} // (if isDarwin then { openclaw-app = openclawApp; } else {})
}
// (if qmdPackage != null then { qmd = qmdPackage; } else { })
// (if isDarwin then { openclaw-app = openclawApp; } else { })

View File

@ -0,0 +1,16 @@
{ stdenv, fetchurl }:
stdenv.mkDerivation {
pname = "node-addon-api";
version = "8.5.0";
src = fetchurl {
url = "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz";
hash = "sha256-0S8HyBYig7YhNVGFXx2o2sFiMxN0YpgwteZA8TDweRA=";
};
dontConfigure = true;
dontBuild = true;
installPhase = "${../scripts/node-addon-api-install.sh}";
}

View File

@ -1,15 +1,16 @@
{ lib
, stdenvNoCC
, fetchzip
{
lib,
stdenvNoCC,
fetchzip,
}:
stdenvNoCC.mkDerivation {
pname = "openclaw-app";
version = "2026.2.13";
version = "2026.5.7";
src = fetchzip {
url = "https://github.com/openclaw/openclaw/releases/download/v2026.2.13/OpenClaw-2026.2.13.zip";
hash = "sha256-ewIZCQ3mg9dus3tD3BdUmESwsk1CFpClbJbLRT1g9Bc=";
url = "https://github.com/openclaw/openclaw/releases/download/v2026.5.7/OpenClaw-2026.5.7.zip";
hash = "sha256-64O1dzadr5R1HiS4DlpbC7En3qyEaibDZS8kKbH7GOo=";
stripRoot = false;
};

View File

@ -1,23 +1,46 @@
{ lib
, buildEnv
, openclaw-gateway
, openclaw-app ? null
, extendedTools ? []
{
lib,
stdenvNoCC,
makeWrapper,
python3Minimal,
openclaw-gateway,
openclaw-app ? null,
extendedTools ? [ ],
qmdPackage ? null,
version ? null,
}:
let
appPaths = lib.optional (openclaw-app != null) openclaw-app;
appLinks = lib.optional (openclaw-app != null) "/Applications";
bundleVersion =
if version != null && version != "" then version else lib.getVersion openclaw-gateway;
runtimeTools = extendedTools ++ lib.optional (qmdPackage != null) qmdPackage;
toolsPath = lib.makeBinPath runtimeTools;
in
buildEnv {
name = "openclaw-2.0.0-beta5";
paths = [ openclaw-gateway ] ++ appPaths ++ extendedTools;
pathsToLink = [ "/bin" ] ++ appLinks;
stdenvNoCC.mkDerivation {
pname = "openclaw";
version = bundleVersion;
dontUnpack = true;
dontConfigure = true;
dontBuild = true;
nativeBuildInputs = [ makeWrapper ];
env = {
OPENCLAW_APP_PACKAGE = lib.optionalString (openclaw-app != null) "${openclaw-app}";
OPENCLAW_GATEWAY_BIN = "${openclaw-gateway}/bin/openclaw";
OPENCLAW_PINNED_WRITE_PYTHON = "${python3Minimal}/bin/python3";
OPENCLAW_TOOLS_PATH = toolsPath;
STDENV_SETUP = "${stdenvNoCC}/setup";
};
installPhase = "${../scripts/openclaw-batteries-install.sh}";
meta = with lib; {
description = "OpenClaw batteries-included bundle (gateway + app + tools)";
homepage = "https://github.com/openclaw/openclaw";
license = licenses.mit;
platforms = platforms.darwin ++ platforms.linux;
mainProgram = "openclaw";
};
}

View File

@ -1,95 +1,89 @@
{ lib
, stdenv
, fetchFromGitHub
, fetchurl
, nodejs_22
, pnpm_10
, pkg-config
, jq
, python3
, perl
, node-gyp
, makeWrapper
, vips
, git
, zstd
, sourceInfo
, gatewaySrc ? null
, pnpmDepsHash ? (sourceInfo.pnpmDepsHash or null)
{
lib,
stdenv,
fetchFromGitHub,
fetchurl,
nodejs_22,
pnpm_10,
fetchPnpmDeps,
pkg-config,
jq,
python3,
perl,
node-gyp,
makeWrapper,
vips,
git,
zstd,
sourceInfo,
gatewaySrc ? null,
pnpmDepsHash ? (sourceInfo.pnpmDepsHash or null),
}:
assert gatewaySrc == null || pnpmDepsHash != null;
let
sourceFetch = lib.removeAttrs sourceInfo [ "pnpmDepsHash" ];
pnpmPlatform = if stdenv.hostPlatform.isDarwin then "darwin" else "linux";
pnpmArch = if stdenv.hostPlatform.isAarch64 then "arm64" else "x64";
nodeAddonApi = stdenv.mkDerivation {
pname = "node-addon-api";
version = "8.5.0";
src = fetchurl {
url = "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz";
hash = "sha256-0S8HyBYig7YhNVGFXx2o2sFiMxN0YpgwteZA8TDweRA=";
};
dontConfigure = true;
dontBuild = true;
installPhase = "${../scripts/node-addon-api-install.sh}";
};
common =
import ../lib/openclaw-gateway-common.nix
{
inherit
lib
stdenv
fetchFromGitHub
fetchurl
nodejs_22
pnpm_10
fetchPnpmDeps
pkg-config
jq
python3
node-gyp
git
zstd
;
}
{
pname = "openclaw-gateway";
sourceInfo = sourceInfo;
pnpmDepsHash = pnpmDepsHash;
pnpmDepsPname = "openclaw-gateway";
gatewaySrc = gatewaySrc;
enableSharp = true;
extraNativeBuildInputs = [
perl
makeWrapper
];
extraBuildInputs = [ vips ];
extraEnv = {
NODE_BIN = "${nodejs_22}/bin/node";
PATCH_CLIPBOARD_SH = "${../scripts/patch-clipboard.sh}";
PATCH_CLIPBOARD_WRAPPER = "${../scripts/clipboard-wrapper.cjs}";
};
};
in
stdenv.mkDerivation (finalAttrs: {
pname = "openclaw-gateway";
version = "2026.1.8-2";
inherit (common) version;
src = if gatewaySrc != null then gatewaySrc else fetchFromGitHub sourceFetch;
src = common.resolvedSrc;
pnpmDeps = common.pnpmDeps;
pnpmDeps = pnpm_10.fetchDeps {
inherit (finalAttrs) pname version src;
hash = if pnpmDepsHash != null
then pnpmDepsHash
else lib.fakeHash;
fetcherVersion = 2;
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
nativeBuildInputs = [ git ];
};
nativeBuildInputs = common.nativeBuildInputs;
buildInputs = common.buildInputs;
nativeBuildInputs = [
nodejs_22
pnpm_10
pkg-config
jq
python3
perl
node-gyp
makeWrapper
zstd
];
buildInputs = [ vips ];
env = {
SHARP_IGNORE_GLOBAL_LIBVIPS = "1";
npm_config_arch = pnpmArch;
npm_config_platform = pnpmPlatform;
PNPM_CONFIG_MANAGE_PACKAGE_MANAGER_VERSIONS = "false";
npm_config_nodedir = nodejs_22;
npm_config_python = python3;
NODE_PATH = "${nodeAddonApi}/lib/node_modules:${node-gyp}/lib/node_modules";
NODE_BIN = "${nodejs_22}/bin/node";
env = common.env // {
# Nix doesn't automatically substitute finalAttrs into env.
PNPM_DEPS = finalAttrs.pnpmDeps;
NODE_GYP_WRAPPER_SH = "${../scripts/node-gyp-wrapper.sh}";
GATEWAY_PREBUILD_SH = "${../scripts/gateway-prebuild.sh}";
PROMOTE_PNPM_INTEGRITY_SH = "${../scripts/promote-pnpm-integrity.sh}";
REMOVE_PACKAGE_MANAGER_FIELD_SH = "${../scripts/remove-package-manager-field.sh}";
PATCH_CLIPBOARD_SH = "${../scripts/patch-clipboard.sh}";
PATCH_CLIPBOARD_WRAPPER = "${../scripts/clipboard-wrapper.cjs}";
STDENV_SETUP = "${stdenv}/setup";
};
passthru = common.passthru;
postPatch = "${../scripts/gateway-postpatch.sh}";
buildPhase = "${../scripts/gateway-build.sh}";
installPhase = "${../scripts/gateway-install.sh}";
dontFixup = true;
dontStrip = true;
dontPatchShebangs = true;

View File

@ -0,0 +1,20 @@
diff --git a/src/plugins/public-surface-loader.ts b/src/plugins/public-surface-loader.ts
index 5f6f939..b8d27c8 100644
--- a/src/plugins/public-surface-loader.ts
+++ b/src/plugins/public-surface-loader.ts
@@ -133,8 +133,12 @@ export function loadBundledPluginPublicArtifactModuleSync<T extends object>(para
- const opened = openRootFileSync({
+ const packageRoot = path.resolve(OPENCLAW_PACKAGE_ROOT);
+ const resolvedModulePath = path.resolve(location.modulePath);
+ const isPackagePublicSurface = resolvedModulePath.startsWith(`${packageRoot}${path.sep}`);
+
+ const opened = openRootFileSync({
absolutePath: location.modulePath,
rootPath: location.boundaryRoot,
boundaryLabel:
location.boundaryRoot === OPENCLAW_PACKAGE_ROOT ? "OpenClaw package root" : "plugin root",
- rejectHardlinks: true,
+ rejectHardlinks: !isPackagePublicSurface,
});
if (!opened.ok) {
throw new Error(

View File

@ -0,0 +1,20 @@
diff --git a/src/plugins/public-surface-loader.ts b/src/plugins/public-surface-loader.ts
index 1f5b5ab..a08ef8a 100644
--- a/src/plugins/public-surface-loader.ts
+++ b/src/plugins/public-surface-loader.ts
@@ -124,8 +124,12 @@ export function loadBundledPluginPublicArtifactModuleSync<T extends object>(para
- const opened = openBoundaryFileSync({
+ const packageRoot = path.resolve(OPENCLAW_PACKAGE_ROOT);
+ const resolvedModulePath = path.resolve(location.modulePath);
+ const isPackagePublicSurface = resolvedModulePath.startsWith(`${packageRoot}${path.sep}`);
+
+ const opened = openBoundaryFileSync({
absolutePath: location.modulePath,
rootPath: location.boundaryRoot,
boundaryLabel:
location.boundaryRoot === OPENCLAW_PACKAGE_ROOT ? "OpenClaw package root" : "plugin root",
- rejectHardlinks: true,
+ rejectHardlinks: !isPackagePublicSurface,
});
if (!opened.ok) {
throw new Error(

View File

@ -0,0 +1,26 @@
diff --git a/src/gateway/server-startup-config.ts b/src/gateway/server-startup-config.ts
index e3f1565a00..97feaf2e8c 100644
--- a/src/gateway/server-startup-config.ts
+++ b/src/gateway/server-startup-config.ts
@@ -99,6 +99,21 @@ export async function loadGatewayStartupConfigSnapshot(params: {
};
}
+ if (isNixMode) {
+ params.log.info(
+ `gateway: auto-enabled plugins for this runtime without writing config in Nix mode:\n${autoEnable.changes.map((entry) => `- ${entry}`).join("\n")}`,
+ );
+ return {
+ snapshot: {
+ ...configSnapshot,
+ runtimeConfig: autoEnable.config,
+ config: autoEnable.config,
+ },
+ wroteConfig,
+ ...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
+ };
+ }
+
try {
const { replaceConfigFile } = await import("../config/mutate.js");
await replaceConfigFile({

View File

@ -0,0 +1,426 @@
import { spawnSync } from "node:child_process";
import { createHash } from "node:crypto";
import fs from "node:fs";
import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { resolveNpmRunner } from "./npm-runner.mjs";
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function writeJson(filePath, value) {
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
function removePathIfExists(targetPath) {
fs.rmSync(targetPath, { recursive: true, force: true });
}
function makeTempDir(parentDir, prefix) {
return fs.mkdtempSync(path.join(parentDir, prefix));
}
function sanitizeTempPrefixSegment(value) {
const normalized = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/-+/g, "-");
return normalized.length > 0 ? normalized : "plugin";
}
function replaceDir(targetPath, sourcePath) {
removePathIfExists(targetPath);
try {
fs.renameSync(sourcePath, targetPath);
return;
} catch (error) {
if (error?.code !== "EXDEV") {
throw error;
}
}
fs.cpSync(sourcePath, targetPath, { recursive: true, force: true });
removePathIfExists(sourcePath);
}
function dependencyNodeModulesPath(nodeModulesDir, depName) {
return path.join(nodeModulesDir, ...depName.split("/"));
}
function createResolver(fromDir) {
return createRequire(path.join(fromDir, "__openclaw-runtime-deps-resolver__.cjs"));
}
function findPackageRoot(startPath, depName) {
let currentDir = fs.statSync(startPath).isDirectory() ? startPath : path.dirname(startPath);
while (true) {
const packageJsonPath = path.join(currentDir, "package.json");
if (fs.existsSync(packageJsonPath)) {
const packageJson = readJson(packageJsonPath);
if (packageJson.name === depName) {
return {
dir: currentDir,
packageJsonPath,
};
}
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
return null;
}
currentDir = parentDir;
}
}
function resolveDependencyFromNodeModulesPath(fromDir, depName) {
let currentDir = fromDir;
while (true) {
const nodeModulesDir =
path.basename(currentDir) === "node_modules" ? currentDir : path.join(currentDir, "node_modules");
const directPath = dependencyNodeModulesPath(nodeModulesDir, depName);
const packageJsonPath = path.join(directPath, "package.json");
if (fs.existsSync(packageJsonPath)) {
return {
dir: directPath,
packageJsonPath,
};
}
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
return null;
}
currentDir = parentDir;
}
}
function resolveInstalledDependency(fromDir, depName) {
const directResolution = resolveDependencyFromNodeModulesPath(fromDir, depName);
if (directResolution !== null) {
return directResolution;
}
const resolver = createResolver(fromDir);
try {
return findPackageRoot(resolver.resolve(`${depName}/package.json`), depName);
} catch {}
try {
return findPackageRoot(resolver.resolve(depName), depName);
} catch {}
return null;
}
function stageInstalledRuntimeTree(rootNodeModulesDir, packageJson, stagedNodeModulesDir) {
const packageCache = new Map();
const stagedTargets = new Set();
const queue = [
...Object.entries(packageJson.dependencies ?? {}).map(([depName, spec]) => ({
depName,
spec,
fromDir: rootNodeModulesDir,
isOptional: false,
targetNodeModulesDir: stagedNodeModulesDir,
})),
...Object.entries(packageJson.optionalDependencies ?? {}).map(([depName, spec]) => ({
depName,
spec,
fromDir: rootNodeModulesDir,
isOptional: true,
targetNodeModulesDir: stagedNodeModulesDir,
})),
];
stageInstalledRuntimeTree.lastFailure = null;
while (queue.length > 0) {
const { depName, fromDir, isOptional, spec, targetNodeModulesDir } = queue.shift();
const resolvedDep = resolveInstalledDependency(fromDir, depName);
if (resolvedDep === null) {
if (isOptional) {
continue;
}
stageInstalledRuntimeTree.lastFailure =
fromDir === rootNodeModulesDir
? `missing ${depName} (${spec}) from root`
: `missing ${depName} (${spec}) from ${fromDir}`;
return false;
}
const packageJson =
packageCache.get(resolvedDep.packageJsonPath) ?? readJson(resolvedDep.packageJsonPath);
packageCache.set(resolvedDep.packageJsonPath, packageJson);
const targetPath = dependencyNodeModulesPath(targetNodeModulesDir, depName);
if (!stagedTargets.has(targetPath)) {
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.cpSync(resolvedDep.dir, targetPath, { recursive: true, force: true, dereference: true });
stagedTargets.add(targetPath);
}
const childTargetNodeModulesDir = path.join(targetPath, "node_modules");
for (const [childName, childSpec] of Object.entries(packageJson.dependencies ?? {})) {
queue.push({
depName: childName,
spec: childSpec,
fromDir: resolvedDep.dir,
isOptional: false,
targetNodeModulesDir: childTargetNodeModulesDir,
});
}
for (const [childName, childSpec] of Object.entries(packageJson.optionalDependencies ?? {})) {
queue.push({
depName: childName,
spec: childSpec,
fromDir: resolvedDep.dir,
isOptional: true,
targetNodeModulesDir: childTargetNodeModulesDir,
});
}
}
return true;
}
function listBundledPluginRuntimeDirs(repoRoot) {
const extensionsRoot = path.join(repoRoot, "dist", "extensions");
if (!fs.existsSync(extensionsRoot)) {
return [];
}
return fs
.readdirSync(extensionsRoot, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => path.join(extensionsRoot, dirent.name))
.filter((pluginDir) => fs.existsSync(path.join(pluginDir, "package.json")));
}
function hasRuntimeDeps(packageJson) {
return (
Object.keys(packageJson.dependencies ?? {}).length > 0 ||
Object.keys(packageJson.optionalDependencies ?? {}).length > 0
);
}
function shouldStageRuntimeDeps(packageJson) {
return packageJson.openclaw?.bundle?.stageRuntimeDependencies === true;
}
function sanitizeBundledManifestForRuntimeInstall(pluginDir) {
const manifestPath = path.join(pluginDir, "package.json");
const packageJson = readJson(manifestPath);
let changed = false;
if (packageJson.peerDependencies?.openclaw) {
const nextPeerDependencies = { ...packageJson.peerDependencies };
delete nextPeerDependencies.openclaw;
if (Object.keys(nextPeerDependencies).length === 0) {
delete packageJson.peerDependencies;
} else {
packageJson.peerDependencies = nextPeerDependencies;
}
changed = true;
}
if (packageJson.peerDependenciesMeta?.openclaw) {
const nextPeerDependenciesMeta = { ...packageJson.peerDependenciesMeta };
delete nextPeerDependenciesMeta.openclaw;
if (Object.keys(nextPeerDependenciesMeta).length === 0) {
delete packageJson.peerDependenciesMeta;
} else {
packageJson.peerDependenciesMeta = nextPeerDependenciesMeta;
}
changed = true;
}
if (packageJson.devDependencies?.openclaw) {
const nextDevDependencies = { ...packageJson.devDependencies };
delete nextDevDependencies.openclaw;
if (Object.keys(nextDevDependencies).length === 0) {
delete packageJson.devDependencies;
} else {
packageJson.devDependencies = nextDevDependencies;
}
changed = true;
}
if (changed) {
writeJson(manifestPath, packageJson);
}
return packageJson;
}
function resolveRuntimeDepsStampPath(pluginDir) {
return path.join(pluginDir, ".openclaw-runtime-deps-stamp.json");
}
function createRuntimeDepsFingerprint(packageJson) {
return createHash("sha256").update(JSON.stringify(packageJson)).digest("hex");
}
function readRuntimeDepsStamp(stampPath) {
if (!fs.existsSync(stampPath)) {
return null;
}
try {
return readJson(stampPath);
} catch {
return null;
}
}
function stageInstalledRootRuntimeDeps(params) {
const { fingerprint, packageJson, pluginDir, repoRoot } = params;
const rootNodeModulesDir = path.join(repoRoot, "node_modules");
const hasDeps =
Object.keys(packageJson.dependencies ?? {}).length > 0 ||
Object.keys(packageJson.optionalDependencies ?? {}).length > 0;
if (!hasDeps || !fs.existsSync(rootNodeModulesDir)) {
return false;
}
const nodeModulesDir = path.join(pluginDir, "node_modules");
const stampPath = resolveRuntimeDepsStampPath(pluginDir);
const stagedNodeModulesDir = path.join(
makeTempDir(
os.tmpdir(),
`openclaw-runtime-deps-${sanitizeTempPrefixSegment(path.basename(pluginDir))}-`,
),
"node_modules",
);
if (!stageInstalledRuntimeTree(rootNodeModulesDir, packageJson, stagedNodeModulesDir)) {
console.error(
`[nix-openclaw] root runtime staging unavailable for ${path.basename(pluginDir)}: ${
stageInstalledRuntimeTree.lastFailure ?? "unknown reason"
}`,
);
return false;
}
try {
replaceDir(nodeModulesDir, stagedNodeModulesDir);
writeJson(stampPath, {
fingerprint,
generatedAt: new Date().toISOString(),
});
return true;
} finally {
removePathIfExists(path.dirname(stagedNodeModulesDir));
}
}
function installPluginRuntimeDeps(params) {
const { fingerprint, packageJson, pluginDir, pluginId, repoRoot } = params;
if (
repoRoot &&
stageInstalledRootRuntimeDeps({ fingerprint, packageJson, pluginDir, repoRoot })
) {
return;
}
console.error(`[nix-openclaw] falling back to npm install for ${pluginId}`);
const nodeModulesDir = path.join(pluginDir, "node_modules");
const stampPath = resolveRuntimeDepsStampPath(pluginDir);
const tempInstallDir = makeTempDir(
os.tmpdir(),
`openclaw-runtime-deps-${sanitizeTempPrefixSegment(pluginId)}-`,
);
const npmRunner = resolveNpmRunner({
npmArgs: [
"install",
"--omit=dev",
"--silent",
"--ignore-scripts",
"--legacy-peer-deps",
"--package-lock=false",
],
});
try {
writeJson(path.join(tempInstallDir, "package.json"), packageJson);
const result = spawnSync(npmRunner.command, npmRunner.args, {
cwd: tempInstallDir,
encoding: "utf8",
env: npmRunner.env,
stdio: "pipe",
shell: npmRunner.shell,
windowsVerbatimArguments: npmRunner.windowsVerbatimArguments,
});
if (result.status !== 0) {
const output = [result.stderr, result.stdout].filter(Boolean).join("\n").trim();
throw new Error(
`failed to stage bundled runtime deps for ${pluginId}: ${output || "npm install failed"}`,
);
}
const stagedNodeModulesDir = path.join(tempInstallDir, "node_modules");
if (!fs.existsSync(stagedNodeModulesDir)) {
throw new Error(
`failed to stage bundled runtime deps for ${pluginId}: npm install produced no node_modules directory`,
);
}
replaceDir(nodeModulesDir, stagedNodeModulesDir);
writeJson(stampPath, {
fingerprint,
generatedAt: new Date().toISOString(),
});
} finally {
removePathIfExists(tempInstallDir);
}
}
function installPluginRuntimeDepsWithRetries(params) {
const { attempts = 3 } = params;
let lastError;
for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
params.install({ ...params.installParams, attempt });
return;
} catch (error) {
lastError = error;
if (attempt === attempts) {
break;
}
}
}
throw lastError;
}
export function stageBundledPluginRuntimeDeps(params = {}) {
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
const installPluginRuntimeDepsImpl =
params.installPluginRuntimeDepsImpl ?? installPluginRuntimeDeps;
const installAttempts = params.installAttempts ?? 3;
for (const pluginDir of listBundledPluginRuntimeDirs(repoRoot)) {
const pluginId = path.basename(pluginDir);
const packageJson = sanitizeBundledManifestForRuntimeInstall(pluginDir);
const nodeModulesDir = path.join(pluginDir, "node_modules");
const stampPath = resolveRuntimeDepsStampPath(pluginDir);
if (!hasRuntimeDeps(packageJson) || !shouldStageRuntimeDeps(packageJson)) {
removePathIfExists(nodeModulesDir);
removePathIfExists(stampPath);
continue;
}
const fingerprint = createRuntimeDepsFingerprint(packageJson);
const stamp = readRuntimeDepsStamp(stampPath);
if (fs.existsSync(nodeModulesDir) && stamp?.fingerprint === fingerprint) {
continue;
}
installPluginRuntimeDepsWithRetries({
attempts: installAttempts,
install: installPluginRuntimeDepsImpl,
installParams: {
fingerprint,
packageJson,
pluginDir,
pluginId,
repoRoot,
},
});
}
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
stageBundledPluginRuntimeDeps();
}

78
nix/scripts/build-root.sh Normal file
View File

@ -0,0 +1,78 @@
#!/bin/sh
openclaw_build_root_file() {
if [ -n "${OPENCLAW_BUILD_ROOT_FILE:-}" ]; then
printf '%s\n' "$OPENCLAW_BUILD_ROOT_FILE"
return
fi
if [ -n "${NIX_BUILD_TOP:-}" ]; then
printf '%s\n' "$NIX_BUILD_TOP/.openclaw-build-root"
return
fi
printf '%s\n' "$PWD/.openclaw-build-root"
}
openclaw_init_output_build_root() {
if [ -z "${out:-}" ]; then
return
fi
build_root="${NIX_BUILD_TOP:-${TMPDIR:-/tmp}}/.openclaw-build"
build_root_file="$(openclaw_build_root_file)"
rm -rf "$build_root"
mkdir -p "$build_root"
( tar -cf - . ) | ( cd "$build_root" && tar -xf - )
chmod -R u+w "$build_root"
printf '%s\n' "$build_root" > "$build_root_file"
cd "$build_root"
}
openclaw_enter_build_root() {
build_root_file="$(openclaw_build_root_file)"
if [ ! -f "$build_root_file" ]; then
return
fi
build_root="$(cat "$build_root_file")"
if [ -n "$build_root" ] && [ -d "$build_root" ]; then
cd "$build_root"
fi
}
openclaw_cleanup_output_pnpm_store() {
build_root_file="$(openclaw_build_root_file)"
build_root=""
store_path=""
if [ -f "$build_root_file" ]; then
build_root="$(cat "$build_root_file")"
fi
if [ -n "$build_root" ] && [ -f "$build_root/${PNPM_STORE_PATH_FILE:-.pnpm-store-path}" ]; then
store_path="$(cat "$build_root/${PNPM_STORE_PATH_FILE:-.pnpm-store-path}")"
fi
cd "${NIX_BUILD_TOP:-/tmp}" 2>/dev/null || cd / || true
case "$store_path" in
"$out"/*) rm -rf "$store_path" ;;
esac
}
openclaw_cleanup_output_build_root() {
build_root_file="$(openclaw_build_root_file)"
build_root=""
if [ -f "$build_root_file" ]; then
build_root="$(cat "$build_root_file")"
fi
openclaw_cleanup_output_pnpm_store
case "$build_root" in
"$out"/*) rm -rf "$build_root" ;;
esac
}

152
nix/scripts/check-config-validity.mjs Executable file → Normal file
View File

@ -1,93 +1,103 @@
#!/usr/bin/env node
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { spawnSync } from "node:child_process";
const configPath = process.env.OPENCLAW_CONFIG_PATH;
const srcRoot = process.env.OPENCLAW_SRC;
const gatewayPackage = process.env.OPENCLAW_GATEWAY;
const expectedWorkspace = process.env.OPENCLAW_EXPECTED_WORKSPACE;
if (!configPath) {
console.error("OPENCLAW_CONFIG_PATH is not set");
process.exit(1);
}
if (!srcRoot) {
console.error("OPENCLAW_SRC is not set");
if (!gatewayPackage) {
console.error("OPENCLAW_GATEWAY is not set");
process.exit(1);
}
const legacyValidationPath = path.join(srcRoot, "dist", "config", "validation.js");
const distDir = path.join(srcRoot, "dist");
if (!expectedWorkspace) {
console.error("OPENCLAW_EXPECTED_WORKSPACE is not set");
process.exit(1);
}
let validateConfigObject = null;
const openclaw = path.join(gatewayPackage, "bin", "openclaw");
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-config-validity-"));
if (fs.existsSync(legacyValidationPath)) {
const moduleUrl = pathToFileURL(legacyValidationPath).href;
const legacyModule = await import(moduleUrl);
validateConfigObject = legacyModule.validateConfigObject;
} else if (fs.existsSync(distDir)) {
const candidates = fs.readdirSync(distDir)
.filter((name) => name.startsWith("config-") && name.endsWith(".js"));
try {
const env = {
...process.env,
HOME: path.join(tmpDir, "home"),
XDG_CONFIG_HOME: path.join(tmpDir, "config"),
XDG_CACHE_HOME: path.join(tmpDir, "cache"),
XDG_DATA_HOME: path.join(tmpDir, "data"),
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_STATE_DIR: path.join(tmpDir, "state"),
OPENCLAW_LOG_DIR: path.join(tmpDir, "logs"),
OPENCLAW_NIX_MODE: "1",
NO_COLOR: "1",
};
for (const candidate of candidates) {
const candidatePath = path.join(distDir, candidate);
const contents = fs.readFileSync(candidatePath, "utf8");
// Newer gateway bundles often only export validateConfigObjectWithPlugins (aliased),
// while still containing an internal validateConfigObject function.
if (!contents.includes("validateConfigObject") && !contents.includes("validateConfigObjectWithPlugins")) {
continue;
}
if (contents.includes("./entry.js")) {
continue;
}
const candidateModule = await import(pathToFileURL(candidatePath).href);
// Prefer the plain validator when exported.
if (typeof candidateModule.validateConfigObject === "function") {
validateConfigObject = candidateModule.validateConfigObject;
break;
}
// Fall back to the plugin-aware validator (what most bundles export today).
if (typeof candidateModule.validateConfigObjectWithPlugins === "function") {
validateConfigObject = candidateModule.validateConfigObjectWithPlugins;
break;
}
// Handle minified alias exports.
let match = contents.match(/validateConfigObject as ([A-Za-z0-9_$]+)/);
if (match && typeof candidateModule[match[1]] === "function") {
validateConfigObject = candidateModule[match[1]];
break;
}
match = contents.match(/validateConfigObjectWithPlugins as ([A-Za-z0-9_$]+)/);
if (match && typeof candidateModule[match[1]] === "function") {
validateConfigObject = candidateModule[match[1]];
break;
}
for (const key of [
"HOME",
"XDG_CONFIG_HOME",
"XDG_CACHE_HOME",
"XDG_DATA_HOME",
"OPENCLAW_STATE_DIR",
"OPENCLAW_LOG_DIR",
]) {
fs.mkdirSync(env[key], { recursive: true });
}
}
if (typeof validateConfigObject !== "function") {
console.error(`Missing validation module: ${legacyValidationPath}`);
process.exit(1);
}
const validate = spawnSync(openclaw, ["config", "validate", "--json"], {
env,
encoding: "utf8",
});
const raw = fs.readFileSync(configPath, "utf8");
const parsed = JSON.parse(raw);
const result = validateConfigObject(parsed);
if (!result.ok) {
console.error("OpenClaw config validation failed:");
for (const issue of result.issues ?? []) {
const pathLabel = issue.path ? ` ${issue.path}` : "";
console.error(`- ${pathLabel}: ${issue.message}`);
if (validate.status !== 0) {
if (validate.stdout) {
process.stdout.write(validate.stdout);
}
if (validate.stderr) {
process.stderr.write(validate.stderr);
}
console.error(`openclaw config validation failed with exit code ${validate.status ?? "unknown"}`);
process.exit(validate.status ?? 1);
}
process.exit(1);
}
console.log("openclaw config validation: ok");
const validation = JSON.parse(validate.stdout);
if (!validation || validation.valid !== true) {
console.error("openclaw config validation did not report valid=true");
process.exit(1);
}
const workspace = spawnSync(openclaw, ["config", "get", "agents.defaults.workspace", "--json"], {
env,
encoding: "utf8",
});
if (workspace.status !== 0) {
if (workspace.stdout) {
process.stdout.write(workspace.stdout);
}
if (workspace.stderr) {
process.stderr.write(workspace.stderr);
}
console.error(`openclaw config get failed with exit code ${workspace.status ?? "unknown"}`);
process.exit(workspace.status ?? 1);
}
const actualWorkspace = JSON.parse(workspace.stdout);
if (actualWorkspace !== expectedWorkspace) {
console.error(
`openclaw config returned unexpected workspace: ${JSON.stringify(actualWorkspace)} != ${JSON.stringify(expectedWorkspace)}`,
);
process.exit(1);
}
console.log("openclaw config validation: ok");
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}

View File

@ -0,0 +1,35 @@
#!/bin/sh
set -eu
if [ -z "${OPENCLAW_PACKAGE:-}" ]; then
echo "OPENCLAW_PACKAGE is not set" >&2
exit 1
fi
bin_dir="${OPENCLAW_PACKAGE}/bin"
openclaw_bin="${bin_dir}/openclaw"
if [ ! -x "$openclaw_bin" ]; then
echo "Missing executable: $openclaw_bin" >&2
exit 1
fi
extra_bins="$(find "$bin_dir" -mindepth 1 -maxdepth 1 -print | while IFS= read -r entry; do
name="$(basename "$entry")"
if [ "$name" != "openclaw" ]; then
printf '%s\n' "$name"
fi
done)"
if [ -n "$extra_bins" ]; then
echo "openclaw package exposes internal runtime tools in bin:" >&2
printf '%s\n' "$extra_bins" >&2
exit 1
fi
if ! grep -q 'PATH' "$openclaw_bin"; then
echo "openclaw wrapper does not set the internal runtime tool PATH" >&2
exit 1
fi
echo "openclaw bin surface: ok"

View File

@ -0,0 +1,85 @@
#!/bin/sh
set -eu
if [ -z "${OPENCLAW_PACKAGE:-}" ]; then
echo "OPENCLAW_PACKAGE is not set" >&2
exit 1
fi
if [ -z "${QMD_PACKAGE:-}" ]; then
echo "QMD_PACKAGE is not set" >&2
exit 1
fi
openclaw_bin="${OPENCLAW_PACKAGE}/bin/openclaw"
qmd_bin="${QMD_PACKAGE}/bin/qmd"
if [ ! -x "$openclaw_bin" ]; then
echo "Missing executable: $openclaw_bin" >&2
exit 1
fi
if [ ! -x "$qmd_bin" ]; then
echo "Missing executable: $qmd_bin" >&2
exit 1
fi
if ! "$qmd_bin" --version >/dev/null; then
echo "qmd --version failed" >&2
exit 1
fi
if ! grep -q "${QMD_PACKAGE}/bin" "$openclaw_bin"; then
echo "openclaw wrapper does not include qmd on the internal runtime PATH" >&2
exit 1
fi
if ! grep -q "OPENCLAW_PINNED_WRITE_PYTHON" "$openclaw_bin"; then
echo "openclaw wrapper does not pin a Nix Python for safe writes" >&2
exit 1
fi
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
mkdir -p "$tmp_dir/home" "$tmp_dir/state" "$tmp_dir/config" "$tmp_dir/cache" "$tmp_dir/data" "$tmp_dir/logs"
cat > "$tmp_dir/state/openclaw.json" <<'JSON'
{
"gateway": {
"mode": "local"
},
"memory": {
"backend": "qmd"
}
}
JSON
env \
HOME="$tmp_dir/home" \
XDG_CONFIG_HOME="$tmp_dir/config" \
XDG_CACHE_HOME="$tmp_dir/cache" \
XDG_DATA_HOME="$tmp_dir/data" \
OPENCLAW_CONFIG_PATH="$tmp_dir/state/openclaw.json" \
OPENCLAW_STATE_DIR="$tmp_dir/state" \
OPENCLAW_LOG_DIR="$tmp_dir/logs" \
OPENCLAW_NIX_MODE=1 \
NO_COLOR=1 \
"$openclaw_bin" config validate --json >/dev/null
backend="$(
env \
HOME="$tmp_dir/home" \
XDG_CONFIG_HOME="$tmp_dir/config" \
XDG_CACHE_HOME="$tmp_dir/cache" \
XDG_DATA_HOME="$tmp_dir/data" \
OPENCLAW_CONFIG_PATH="$tmp_dir/state/openclaw.json" \
OPENCLAW_STATE_DIR="$tmp_dir/state" \
OPENCLAW_LOG_DIR="$tmp_dir/logs" \
OPENCLAW_NIX_MODE=1 \
NO_COLOR=1 \
"$openclaw_bin" config get memory.backend --json
)"
if [ "$backend" != '"qmd"' ]; then
echo "OpenClaw did not read opt-in QMD memory config: $backend" >&2
exit 1
fi
echo "openclaw qmd runtime: ok"

View File

@ -17,11 +17,76 @@ require_path() {
require_path "${root}/extensions"
require_path "${root}/extensions/memory-core"
require_path "${root}/extensions/memory-core/openclaw.plugin.json"
require_path "${root}/dist/extensions/memory-core/openclaw.plugin.json"
require_path "${root}/dist-runtime/extensions"
require_path "${root}/dist-runtime/extensions/memory-core/openclaw.plugin.json"
require_path "${root}/dist-runtime/extensions/acpx/openclaw.plugin.json"
require_path "${root}/dist-runtime/extensions/acpx/package.json"
require_path "${root}/dist-runtime/extensions/acpx/index.js"
require_path "${root}/dist-runtime/extensions/acpx/error-format.mjs"
require_path "${root}/dist-runtime/extensions/acpx/mcp-command-line.mjs"
require_path "${root}/dist-runtime/extensions/acpx/mcp-proxy.mjs"
require_path "${root}/docs/reference/templates"
require_path "${root}/docs/reference/templates/AGENTS.md"
require_path "${root}/docs/reference/templates/SOUL.md"
require_path "${root}/docs/reference/templates/TOOLS.md"
require_path "${root}/skills"
require_path "${root}/node_modules/hasown"
require_path "${root}/node_modules/combined-stream"
public_surface_loader="$(
find "${root}/dist" -name "*.js" -type f -exec grep -sl "function loadBundledPluginPublicArtifactModuleSync" {} + | head -1
)"
if [ -z "$public_surface_loader" ]; then
echo "Missing bundled plugin public surface loader" >&2
exit 1
fi
if grep -q "rejectHardlinks: true" "$public_surface_loader"; then
echo "Bundled plugin public surface loader still rejects hardlinked package files" >&2
exit 1
fi
export PUBLIC_SURFACE_LOADER="$public_surface_loader"
node --input-type=module <<'NODE'
import { pathToFileURL } from "node:url";
const loaderPath = process.env.PUBLIC_SURFACE_LOADER;
if (!loaderPath) {
throw new Error("PUBLIC_SURFACE_LOADER is not set");
}
const loader = await import(pathToFileURL(loaderPath).href);
const loadBundledPluginPublicArtifactModuleSync =
loader.loadBundledPluginPublicArtifactModuleSync ?? loader.t;
if (typeof loadBundledPluginPublicArtifactModuleSync !== "function") {
throw new Error("Bundled plugin public surface loader export not found");
}
loadBundledPluginPublicArtifactModuleSync({
dirName: "openai",
artifactBasename: "provider-policy-api.js",
});
NODE
require_js_alias_target() {
alias="$1"
alias_path="${root}/dist/${alias}"
require_path "$alias_path"
target="$(sed -n 's/^export \* from "\.\/\(.*\)";$/\1/p' "$alias_path" | head -1)"
if [ -z "$target" ]; then
echo "Alias has no export target: $alias_path" >&2
exit 1
fi
require_path "${root}/dist/${target}"
}
require_js_alias_target "runtime-model-auth.runtime.js"
if ! find "${root}/skills" -name SKILL.md -type f | grep -q .; then
echo "Missing bundled SKILL.md files under ${root}/skills" >&2
exit 1
fi
echo "openclaw package contents: ok"

View File

@ -42,15 +42,21 @@ fi
cp "$CONFIG_OPTIONS_GENERATOR" ./generate-config-options.ts
cp "$NODE_ENGINE_CHECK" ./check-node-engine.ts
if [ ! -x "./node_modules/.bin/tsx" ]; then
echo "tsx not found at ./node_modules/.bin/tsx (run gateway-tests-build.sh first)" >&2
if ! command -v node >/dev/null 2>&1; then
echo "node not found in PATH (run source-checks-build.sh first)" >&2
exit 1
fi
./node_modules/.bin/tsx ./check-node-engine.ts --repo .
tsx_cli="./node_modules/tsx/dist/cli.mjs"
if [ ! -f "$tsx_cli" ]; then
echo "tsx CLI not found at $tsx_cli (run source-checks-build.sh first)" >&2
exit 1
fi
node "$tsx_cli" ./check-node-engine.ts --repo .
output_path="./generated-config-options.nix"
./node_modules/.bin/tsx ./generate-config-options.ts --repo . --out "$output_path"
node "$tsx_cli" ./generate-config-options.ts --repo . --out "$output_path"
diff -u "$CONFIG_OPTIONS_GOLDEN" "$output_path"

View File

@ -1,6 +1,22 @@
#!/bin/sh
set -e
log_step() {
if [ "${OPENCLAW_NIX_TIMINGS:-1}" != "1" ]; then
"$@"
return
fi
name="$1"
shift
start=$(date +%s)
printf '>> [timing] %s...\n' "$name" >&2
"$@"
end=$(date +%s)
printf '>> [timing] %s: %ss\n' "$name" "$((end - start))" >&2
}
if [ -z "${GATEWAY_PREBUILD_SH:-}" ]; then
echo "GATEWAY_PREBUILD_SH is not set" >&2
exit 1
@ -27,13 +43,87 @@ export NPM_CONFIG_STORE_DIR="$store_path"
export NPM_CONFIG_STORE_PATH="$store_path"
export HOME="$(mktemp -d)"
pnpm install --offline --frozen-lockfile --ignore-scripts --store-dir "$store_path"
chmod -R u+w node_modules
log_step "pnpm install (offline, frozen, ignore-scripts)" pnpm install --offline --frozen-lockfile --ignore-scripts --store-dir "$store_path"
log_step "chmod node_modules writable" chmod -R u+w node_modules
# sharp may leave build artifacts around; remove to keep output smaller + avoid stale builds.
rm -rf node_modules/.pnpm/sharp@*/node_modules/sharp/src/build
# Rebuild only native deps (avoid `pnpm rebuild` over the entire workspace).
# node-llama-cpp postinstall attempts to download/compile llama.cpp (network blocked in Nix).
NODE_LLAMA_CPP_SKIP_DOWNLOAD=1 pnpm rebuild
bash -e -c ". \"$STDENV_SETUP\"; patchShebangs node_modules/.bin"
pnpm build
pnpm ui:build
CI=true pnpm prune --prod
# Also defensively disable other common downloaders.
rebuild_list="$(jq -r '.pnpm.onlyBuiltDependencies // [] | .[]' package.json 2>/dev/null || true)"
if [ -n "$rebuild_list" ]; then
log_step "pnpm rebuild (onlyBuiltDependencies)" env \
NODE_LLAMA_CPP_SKIP_DOWNLOAD=1 \
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \
PUPPETEER_SKIP_DOWNLOAD=1 \
ELECTRON_SKIP_BINARY_DOWNLOAD=1 \
pnpm rebuild $rebuild_list
else
log_step "pnpm rebuild (all)" env \
NODE_LLAMA_CPP_SKIP_DOWNLOAD=1 \
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \
PUPPETEER_SKIP_DOWNLOAD=1 \
ELECTRON_SKIP_BINARY_DOWNLOAD=1 \
pnpm rebuild
fi
log_step "patchShebangs node_modules/.bin" bash -e -c ". \"$STDENV_SETUP\"; patchShebangs node_modules/.bin"
# Git tarball dependencies do not get their npm prepack output in offline Nix
# builds. OpenClaw currently depends on @openclaw/fs-safe this way.
if [ -n "${OPENCLAW_FS_SAFE_SOURCE:-}" ] && [ ! -d "node_modules/@openclaw/fs-safe/dist" ]; then
rm -rf node_modules/@openclaw/fs-safe
mkdir -p node_modules/@openclaw
cp -R "$OPENCLAW_FS_SAFE_SOURCE" node_modules/@openclaw/fs-safe
chmod -R u+w node_modules/@openclaw/fs-safe
log_step "build dependency: @openclaw/fs-safe" pnpm exec tsc -p node_modules/@openclaw/fs-safe/tsconfig.json
fi
# Ensure rolldown is found from workspace bins in offline/sandbox builds.
if [ -d "node_modules/.pnpm/node_modules/.bin" ]; then
export PATH="$PWD/node_modules/.pnpm/node_modules/.bin:$PATH"
fi
# Break down `pnpm build` (upstream package.json) so we can profile it while
# still using upstream's asset hooks. v2026.5.7 has the older canvas-only helper;
# newer OpenClaw has the generic bundled-plugin asset runner.
if [ -f "scripts/bundled-plugin-assets.mjs" ]; then
log_step "build: plugins:assets:build" node scripts/bundled-plugin-assets.mjs --phase build
else
log_step "build: canvas:a2ui:bundle" node scripts/bundle-a2ui.mjs
fi
log_step "build: tsdown" pnpm exec tsdown
log_step "build: runtime-postbuild" node scripts/runtime-postbuild.mjs
if [ -f "scripts/stage-bundled-plugin-runtime.mjs" ]; then
log_step "build: stage bundled plugin runtime" node scripts/stage-bundled-plugin-runtime.mjs
fi
log_step "build: plugin-sdk dts" pnpm build:plugin-sdk:dts
log_step "build: write-plugin-sdk-entry-dts" node --import tsx scripts/write-plugin-sdk-entry-dts.ts
if [ -f "scripts/copy-plugin-sdk-root-alias.mjs" ]; then
log_step "build: copy-plugin-sdk-root-alias" node scripts/copy-plugin-sdk-root-alias.mjs
fi
if [ -f "scripts/copy-bundled-plugin-metadata.mjs" ]; then
log_step "build: copy-bundled-plugin-metadata" node scripts/copy-bundled-plugin-metadata.mjs
fi
if [ -f "scripts/bundled-plugin-assets.mjs" ]; then
log_step "build: plugins:assets:copy" node scripts/bundled-plugin-assets.mjs --phase copy
else
log_step "build: canvas-a2ui-copy" node --import tsx scripts/canvas-a2ui-copy.ts
fi
log_step "build: copy-hook-metadata" node --import tsx scripts/copy-hook-metadata.ts
log_step "build: write-build-info" node --import tsx scripts/write-build-info.ts
log_step "build: write-cli-compat" node --import tsx scripts/write-cli-compat.ts
log_step "ui:build" pnpm ui:build
log_step "pnpm prune --prod" env CI=true pnpm prune --prod
# Reduce output size (pnpm implementation detail; safe to remove)
rm -rf node_modules/.pnpm/node_modules
# pnpm prune can leave orphaned .bin links behind for removed prod deps.
# Keep install-phase symlink validation strict by dropping only broken links here.
find node_modules -xtype l -delete

View File

@ -1,15 +1,90 @@
#!/bin/sh
set -e
log_step() {
if [ "${OPENCLAW_NIX_TIMINGS:-1}" != "1" ]; then
"$@"
return
fi
name="$1"
shift
start=$(date +%s)
printf '>> [timing] %s...\n' "$name" >&2
"$@"
end=$(date +%s)
printf '>> [timing] %s: %ss\n' "$name" "$((end - start))" >&2
}
if [ -n "${OPENCLAW_BUILD_ROOT_SH:-}" ]; then
. "$OPENCLAW_BUILD_ROOT_SH"
openclaw_enter_build_root
fi
check_no_broken_symlinks() {
root="$1"
if [ ! -d "$root" ]; then
return 0
fi
broken_tmp="$(mktemp)"
# Portable and faster than `find ... -exec test -e {} \;` on large trees.
find "$root" -type l -print | while IFS= read -r link; do
[ -e "$link" ] || printf '%s\n' "$link"
done > "$broken_tmp"
if [ -s "$broken_tmp" ]; then
echo "dangling symlinks found under $root" >&2
cat "$broken_tmp" >&2
rm -f "$broken_tmp"
return 1
fi
rm -f "$broken_tmp"
}
copy_extension_manifests() {
if [ ! -d extensions ]; then
return 0
fi
mkdir -p "$out/lib/openclaw/extensions"
find extensions -mindepth 2 -maxdepth 2 -name openclaw.plugin.json -type f -print | while IFS= read -r manifest; do
name="$(basename "$(dirname "$manifest")")"
mkdir -p "$out/lib/openclaw/extensions/$name"
cp "$manifest" "$out/lib/openclaw/extensions/$name/openclaw.plugin.json"
done
}
mkdir -p "$out/lib/openclaw" "$out/bin"
cp -r dist node_modules package.json ui "$out/lib/openclaw/"
set -- dist node_modules package.json
if [ -d dist-runtime ]; then
set -- "$@" dist-runtime
fi
log_step "copy build outputs" cp -R "$@" "$out/lib/openclaw/"
if [ -d extensions ]; then
cp -r extensions "$out/lib/openclaw/"
log_step "copy extension manifests" copy_extension_manifests
fi
if [ -d skills ]; then
log_step "copy bundled skills" cp -r skills "$out/lib/openclaw/"
fi
# Gateway plugin discovery looks under dist/extensions/*/openclaw.plugin.json.
# Upstream's build emits JS into dist/extensions but leaves manifests in extensions/.
if [ -d "$out/lib/openclaw/extensions" ] && [ -d "$out/lib/openclaw/dist/extensions" ]; then
for manifest in "$out/lib/openclaw/extensions"/*/openclaw.plugin.json; do
[ -f "$manifest" ] || continue
name="$(basename "$(dirname "$manifest")")"
dist_ext="$out/lib/openclaw/dist/extensions/$name"
if [ -d "$dist_ext" ] && [ ! -f "$dist_ext/openclaw.plugin.json" ]; then
cp "$manifest" "$dist_ext/openclaw.plugin.json"
fi
done
fi
if [ -d docs/reference/templates ]; then
mkdir -p "$out/lib/openclaw/docs/reference"
cp -r docs/reference/templates "$out/lib/openclaw/docs/reference/"
log_step "copy reference templates" cp -r docs/reference/templates "$out/lib/openclaw/docs/reference/"
fi
if [ -z "${STDENV_SETUP:-}" ]; then
@ -21,11 +96,6 @@ if [ ! -f "$STDENV_SETUP" ]; then
exit 1
fi
bash -e -c '. "$STDENV_SETUP"; patchShebangs "$out/lib/openclaw/node_modules/.bin"'
if [ -d "$out/lib/openclaw/ui/node_modules/.bin" ]; then
bash -e -c '. "$STDENV_SETUP"; patchShebangs "$out/lib/openclaw/ui/node_modules/.bin"'
fi
# Work around missing dependency declaration in pi-coding-agent (strip-ansi).
# Ensure it is resolvable at runtime without changing upstream.
pi_pkg="$(find "$out/lib/openclaw/node_modules/.pnpm" -path "*/node_modules/@mariozechner/pi-coding-agent" -print | head -n 1)"
@ -80,4 +150,13 @@ if [ -n "$hasown_src" ]; then
fi
fi
bash -e -c '. "$STDENV_SETUP"; makeWrapper "$NODE_BIN" "$out/bin/openclaw" --add-flags "$out/lib/openclaw/dist/index.js" --set-default OPENCLAW_NIX_MODE "1"'
if [ -n "${OPENCLAW_BUILD_ROOT_SH:-}" ]; then
openclaw_cleanup_output_pnpm_store
fi
log_step "validate node_modules symlinks" check_no_broken_symlinks "$out/lib/openclaw/node_modules"
if [ -d "$out/lib/openclaw/dist-runtime" ]; then
log_step "validate dist-runtime symlinks" check_no_broken_symlinks "$out/lib/openclaw/dist-runtime"
fi
log_step "wrap openclaw" bash -e -c '. "$STDENV_SETUP"; makeWrapper "$NODE_BIN" "$out/bin/openclaw" --add-flags "$out/lib/openclaw/dist/index.js" --set-default OPENCLAW_NIX_MODE "1"'

View File

@ -4,6 +4,19 @@ if [ -f package.json ]; then
"$REMOVE_PACKAGE_MANAGER_FIELD_SH" package.json
fi
if [ -n "${PATCH_BUNDLED_RUNTIME_DEPS_SCRIPT:-}" ] && [ -f scripts/stage-bundled-plugin-runtime-deps.mjs ]; then
cp "$PATCH_BUNDLED_RUNTIME_DEPS_SCRIPT" scripts/stage-bundled-plugin-runtime-deps.mjs
chmod u+w scripts/stage-bundled-plugin-runtime-deps.mjs
fi
if [ -n "${PATCH_PUBLIC_SURFACE_HARDLINKS:-}" ]; then
patch -p1 < "$PATCH_PUBLIC_SURFACE_HARDLINKS"
fi
if [ -n "${PATCH_SKIP_PLUGIN_AUTO_ENABLE_NIX_MODE:-}" ]; then
patch -p1 < "$PATCH_SKIP_PLUGIN_AUTO_ENABLE_NIX_MODE"
fi
if [ -f src/logging/logger.ts ]; then
if ! grep -q "OPENCLAW_LOG_DIR" src/logging/logger.ts; then
sed -i 's/export const DEFAULT_LOG_DIR = "\/tmp\/openclaw";/export const DEFAULT_LOG_DIR = process.env.OPENCLAW_LOG_DIR ?? "\/tmp\/openclaw";/' src/logging/logger.ts
@ -41,3 +54,61 @@ if [ -f src/docker-setup.test.ts ]; then
sed -i 's|if \[\[ "${1:-}" == "compose" \]\]; then|if [ "${1:-}" = "compose" ]; then|' src/docker-setup.test.ts
fi
fi
if [ -f src/gateway/test-helpers.mocks.ts ]; then
if ! grep -q 'augmentModelCatalogWithProviderPlugins: async () => \[\]' src/gateway/test-helpers.mocks.ts; then
python3 - <<'PY'
from pathlib import Path
path = Path("src/gateway/test-helpers.mocks.ts")
text = path.read_text()
needle = '''vi.mock("../plugins/loader.js", async () => {
const actual =
await vi.importActual<typeof import("../plugins/loader.js")>("../plugins/loader.js");
return {
...actual,
loadOpenClawPlugins: () => getTestPluginRegistry(),
};
});
'''
replacement = needle + '''
vi.mock("../plugins/provider-runtime.runtime.js", async () => {
const actual = await vi.importActual<typeof import("../plugins/provider-runtime.runtime.js")>(
"../plugins/provider-runtime.runtime.js",
);
return {
...actual,
augmentModelCatalogWithProviderPlugins: async () => [],
};
});
vi.mock("../plugins/web-search-providers.runtime.js", () => ({
resolvePluginWebSearchProviders: () => [],
resolveRuntimeWebSearchProviders: () => [],
__testing: {
resetWebSearchProviderSnapshotCacheForTests: () => {},
},
}));
vi.mock("../plugins/web-fetch-providers.runtime.js", () => ({
resolvePluginWebFetchProviders: () => [],
resolveRuntimeWebFetchProviders: () => [],
__testing: {
resetWebFetchProviderSnapshotCacheForTests: () => {},
},
}));
vi.mock("../plugins/web-provider-public-artifacts.explicit.js", async () => {
const actual =
await vi.importActual<typeof import("../plugins/web-provider-public-artifacts.explicit.js")>(
"../plugins/web-provider-public-artifacts.explicit.js",
);
return {
...actual,
resolveBundledExplicitWebSearchProvidersFromPublicArtifacts: () => [],
resolveBundledExplicitWebFetchProvidersFromPublicArtifacts: () => [],
};
});
'''
if needle not in text:
raise SystemExit("gateway test mocks loader marker not found")
path.write_text(text.replace(needle, replacement, 1))
PY
fi
fi

View File

@ -1,23 +1,59 @@
#!/bin/sh
set -e
log_step() {
if [ "${OPENCLAW_NIX_TIMINGS:-1}" != "1" ]; then
"$@"
return
fi
name="$1"
shift
start=$(date +%s)
printf '>> [timing] %s...\n' "$name" >&2
"$@"
end=$(date +%s)
printf '>> [timing] %s: %ss\n' "$name" "$((end - start))" >&2
}
store_path_file="${PNPM_STORE_PATH_FILE:-.pnpm-store-path}"
store_path="$(mktemp -d)"
if [ -n "${OPENCLAW_BUILD_ROOT_SH:-}" ]; then
. "$OPENCLAW_BUILD_ROOT_SH"
openclaw_init_output_build_root
fi
if [ -n "${out:-}" ]; then
store_path="$out/.pnpm-store"
rm -rf "$store_path"
mkdir -p "$store_path"
else
store_path="$(mktemp -d)"
fi
printf "%s" "$store_path" > "$store_path_file"
fetcherVersion=$(cat "$PNPM_DEPS/.fetcher-version" 2>/dev/null || echo 1)
if [ "$fetcherVersion" -ge 3 ]; then
tar --zstd -xf "$PNPM_DEPS/pnpm-store.tar.zst" -C "$store_path"
# tar --zstd uses libzstd; on some platforms it ends up single-threaded.
# Use zstd directly, bounded by Nix's build-core budget.
zstd_threads="${NIX_BUILD_CORES:-2}"
case "$zstd_threads" in
''|*[!0-9]*) zstd_threads=2 ;;
esac
log_step "extract pnpm store (fetcherVersion=${fetcherVersion})" sh -c '
zstd -d --threads="$3" < "$1" | tar -xf - -C "$2"
' sh "$PNPM_DEPS/pnpm-store.tar.zst" "$store_path" "$zstd_threads"
else
cp -Tr "$PNPM_DEPS" "$store_path"
log_step "copy pnpm store (fetcherVersion=${fetcherVersion})" cp -Tr "$PNPM_DEPS" "$store_path"
fi
chmod -R +w "$store_path"
log_step "chmod pnpm store writable" chmod -R +w "$store_path"
# pnpm --ignore-scripts marks tarball deps as "not built" and offline install
# later refuses to use them; if a dep doesn't require build, promote it.
"$PROMOTE_PNPM_INTEGRITY_SH" "$store_path"
log_step "promote pnpm integrity" "$PROMOTE_PNPM_INTEGRITY_SH" "$store_path"
export REAL_NODE_GYP="$(command -v node-gyp)"
wrapper_dir="$(mktemp -d)"

View File

@ -0,0 +1,202 @@
#!/usr/bin/env node
import crypto from "node:crypto";
import fs from "node:fs";
import net from "node:net";
import os from "node:os";
import path from "node:path";
import { once } from "node:events";
import { spawn, spawnSync } from "node:child_process";
const gatewayPackage = process.env.OPENCLAW_GATEWAY;
if (!gatewayPackage) {
console.error("OPENCLAW_GATEWAY is not set");
process.exit(1);
}
const openclaw = path.join(gatewayPackage, "bin", "openclaw");
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-gateway-smoke-"));
const token = `smoke-${crypto.randomUUID()}`;
const logs = { stdout: "", stderr: "" };
function appendLog(name, chunk) {
logs[name] += chunk.toString();
if (logs[name].length > 12000) {
logs[name] = logs[name].slice(-12000);
}
}
async function freePort() {
const server = net.createServer();
server.listen(0, "127.0.0.1");
await once(server, "listening");
const address = server.address();
const port = typeof address === "object" && address ? address.port : null;
await new Promise((resolve) => server.close(resolve));
if (!port) {
throw new Error("failed to allocate a local port");
}
return port;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function stopProcess(child) {
if (child.exitCode !== null || child.signalCode !== null) {
return;
}
child.kill("SIGTERM");
const stopped = await Promise.race([
once(child, "exit").then(() => true),
sleep(3000).then(() => false),
]);
if (!stopped) {
child.kill("SIGKILL");
await once(child, "exit").catch(() => {});
}
}
function isolatedEnv() {
const env = {
...process.env,
HOME: path.join(tmpDir, "home"),
XDG_CONFIG_HOME: path.join(tmpDir, "config"),
XDG_CACHE_HOME: path.join(tmpDir, "cache"),
XDG_DATA_HOME: path.join(tmpDir, "data"),
OPENCLAW_CONFIG_PATH: path.join(tmpDir, "state", "openclaw.json"),
OPENCLAW_STATE_DIR: path.join(tmpDir, "state"),
OPENCLAW_LOG_DIR: path.join(tmpDir, "logs"),
OPENCLAW_NIX_MODE: "1",
NO_COLOR: "1",
};
for (const key of [
"HOME",
"XDG_CONFIG_HOME",
"XDG_CACHE_HOME",
"XDG_DATA_HOME",
"OPENCLAW_STATE_DIR",
"OPENCLAW_LOG_DIR",
]) {
fs.mkdirSync(env[key], { recursive: true });
}
return env;
}
const env = isolatedEnv();
let gateway = null;
let gatewayHealthy = false;
try {
const version = spawnSync(openclaw, ["--version"], {
env,
encoding: "utf8",
});
if (version.status !== 0 || !version.stdout.trim()) {
process.stdout.write(version.stdout ?? "");
process.stderr.write(version.stderr ?? "");
throw new Error("openclaw --version failed");
}
const port = await freePort();
gateway = spawn(
openclaw,
[
"gateway",
"run",
"--allow-unconfigured",
"--bind",
"loopback",
"--port",
String(port),
"--auth",
"token",
"--token",
token,
"--ws-log",
"compact",
],
{
cwd: tmpDir,
env,
stdio: ["ignore", "pipe", "pipe"],
},
);
gateway.stdout.on("data", (chunk) => appendLog("stdout", chunk));
gateway.stderr.on("data", (chunk) => appendLog("stderr", chunk));
const deadline = Date.now() + 30000;
let lastError = "";
while (Date.now() < deadline) {
if (gateway.exitCode !== null || gateway.signalCode !== null) {
throw new Error(`gateway exited before health check: ${gateway.exitCode ?? gateway.signalCode}`);
}
const health = spawnSync(
openclaw,
[
"gateway",
"health",
"--url",
`ws://127.0.0.1:${port}`,
"--token",
token,
"--json",
"--timeout",
"3000",
],
{
cwd: tmpDir,
env,
encoding: "utf8",
},
);
if (health.status === 0) {
let parsed;
try {
parsed = JSON.parse(health.stdout);
} catch (err) {
lastError = `health returned invalid JSON: ${health.stdout}${health.stderr}`;
await sleep(500);
continue;
}
if (parsed?.ok === true) {
console.log(`openclaw gateway smoke: ok (${version.stdout.trim()})`);
gatewayHealthy = true;
break;
}
lastError = `health JSON did not contain ok=true: ${health.stdout}`;
} else {
lastError = `${health.stdout}${health.stderr}`;
}
await sleep(500);
}
if (!gatewayHealthy) {
throw new Error(`gateway health did not become ready: ${lastError.trim()}`);
}
} catch (err) {
console.error(String(err));
if (logs.stdout.trim()) {
console.error("--- gateway stdout ---");
console.error(logs.stdout.trim());
}
if (logs.stderr.trim()) {
console.error("--- gateway stderr ---");
console.error(logs.stderr.trim());
}
process.exitCode = 1;
} finally {
if (gateway) {
await stopProcess(gateway);
}
fs.rmSync(tmpDir, { recursive: true, force: true });
}

View File

@ -1,31 +0,0 @@
#!/bin/sh
set -e
if [ -z "${GATEWAY_PREBUILD_SH:-}" ]; then
echo "GATEWAY_PREBUILD_SH is not set" >&2
exit 1
fi
. "$GATEWAY_PREBUILD_SH"
store_path_file="${PNPM_STORE_PATH_FILE:-.pnpm-store-path}"
if [ ! -f "$store_path_file" ]; then
echo "pnpm store path file missing: $store_path_file" >&2
exit 1
fi
store_path="$(cat "$store_path_file")"
export PNPM_STORE_DIR="$store_path"
export PNPM_STORE_PATH="$store_path"
export NPM_CONFIG_STORE_DIR="$store_path"
export NPM_CONFIG_STORE_PATH="$store_path"
export HOME="$(mktemp -d)"
pnpm install --offline --frozen-lockfile --ignore-scripts --store-dir "$store_path"
if [ -z "${STDENV_SETUP:-}" ]; then
echo "STDENV_SETUP is not set" >&2
exit 1
fi
if [ ! -f "$STDENV_SETUP" ]; then
echo "STDENV_SETUP not found: $STDENV_SETUP" >&2
exit 1
fi
bash -e -c ". \"$STDENV_SETUP\"; patchShebangs node_modules/.bin"

View File

@ -1,25 +0,0 @@
#!/bin/sh
set -e
store_path_file="${PNPM_STORE_PATH_FILE:-.pnpm-store-path}"
if [ -f "$store_path_file" ]; then
store_path="$(cat "$store_path_file")"
export PNPM_STORE_DIR="$store_path"
export PNPM_STORE_PATH="$store_path"
export NPM_CONFIG_STORE_DIR="$store_path"
export NPM_CONFIG_STORE_PATH="$store_path"
fi
export HOME="$(mktemp -d)"
export TMPDIR="${HOME}/tmp"
mkdir -p "$TMPDIR"
export OPENCLAW_LOG_DIR="${TMPDIR}/openclaw-logs"
mkdir -p "$OPENCLAW_LOG_DIR"
mkdir -p /tmp/openclaw || true
chmod 700 /tmp/openclaw || true
export OPENCLAW_BUNDLED_PLUGINS_DIR="${TMPDIR}/openclaw-empty-extensions"
mkdir -p "$OPENCLAW_BUNDLED_PLUGINS_DIR"
export VITEST_POOL="forks"
export VITEST_MIN_WORKERS="2"
export VITEST_MAX_WORKERS="2"
pnpm vitest run --config vitest.gateway.config.ts --testTimeout=20000

View File

@ -11,6 +11,7 @@ const argValue = (flag: string): string | null => {
const repo = argValue("--repo") ?? process.cwd();
const outPath = argValue("--out") ?? path.join(process.cwd(), "nix/generated/openclaw-config-options.nix");
const schemaRev = argValue("--rev") ?? process.env.OPENCLAW_SCHEMA_REV ?? null;
const schemaPath = path.join(repo, "src/config/zod-schema.ts");
const schemaUrl = pathToFileURL(schemaPath).href;
@ -129,16 +130,16 @@ const stripNullable = (schemaObj: JsonSchema): { schema: JsonSchema; nullable: b
return { schema, nullable: false };
};
const typeForSchema = (schemaObj: JsonSchema, indent: string): string => {
const typeForSchema = (schemaObj: JsonSchema, indent: string, pathSegments: string[] = []): string => {
const { schema, nullable } = stripNullable(schemaObj);
const typeExpr = baseTypeForSchema(schema, indent);
const typeExpr = baseTypeForSchema(schema, indent, pathSegments);
if (nullable) {
return `t.nullOr (${typeExpr})`;
}
return typeExpr;
};
const baseTypeForSchema = (schemaObj: JsonSchema, indent: string): string => {
const baseTypeForSchema = (schemaObj: JsonSchema, indent: string, pathSegments: string[]): string => {
const schema = deref(schemaObj, new Set());
if (schema.const !== undefined) {
return `t.enum [ ${nixLiteral(schema.const)} ]`;
@ -150,13 +151,17 @@ const baseTypeForSchema = (schemaObj: JsonSchema, indent: string): string => {
if (schema.anyOf && Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
const entries = schema.anyOf as JsonSchema[];
const parts = entries.map((entry) => `(${typeForSchema(entry, indent)})`).join(" ");
const objectUnion = objectUnionTypeForSchemas(entries, indent);
if (objectUnion) return objectUnion;
const parts = entries.map((entry) => `(${typeForSchema(entry, indent, pathSegments)})`).join(" ");
return `t.oneOf [ ${parts} ]`;
}
if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
const entries = schema.oneOf as JsonSchema[];
const parts = entries.map((entry) => `(${typeForSchema(entry, indent)})`).join(" ");
const objectUnion = objectUnionTypeForSchemas(entries, indent);
if (objectUnion) return objectUnion;
const parts = entries.map((entry) => `(${typeForSchema(entry, indent, pathSegments)})`).join(" ");
return `t.oneOf [ ${parts} ]`;
}
@ -167,7 +172,7 @@ const baseTypeForSchema = (schemaObj: JsonSchema, indent: string): string => {
const schemaType = schema.type;
if (Array.isArray(schemaType) && schemaType.length > 0) {
const parts = schemaType
.map((entry) => `(${typeForSchema({ type: entry }, indent)})`)
.map((entry) => `(${typeForSchema({ type: entry }, indent, pathSegments)})`)
.join(" ");
return `t.oneOf [ ${parts} ]`;
}
@ -183,13 +188,13 @@ const baseTypeForSchema = (schemaObj: JsonSchema, indent: string): string => {
return "t.bool";
case "array": {
const items = (schema.items as JsonSchema) || {};
return `t.listOf (${typeForSchema(items, indent)})`;
return `t.listOf (${typeForSchema(items, indent, pathSegments)})`;
}
case "object":
return objectTypeForSchema(schema, indent);
return objectTypeForSchema(schema, indent, pathSegments);
case undefined:
if (schema.properties || schema.additionalProperties) {
return objectTypeForSchema(schema, indent);
return objectTypeForSchema(schema, indent, pathSegments);
}
return "t.anything";
default:
@ -197,7 +202,72 @@ const baseTypeForSchema = (schemaObj: JsonSchema, indent: string): string => {
}
};
const objectTypeForSchema = (schema: JsonSchema, indent: string): string => {
const objectUnionTypeForSchemas = (entries: JsonSchema[], indent: string): string | null => {
const discriminator = "source";
const variants = entries.map((entry) => deref(entry, new Set()));
const propsByVariant = variants.map((entry) => (entry.properties as Record<string, JsonSchema>) || null);
if (propsByVariant.some((props) => props === null)) return null;
const requiredByVariant = variants.map((entry) => new Set((entry.required as string[]) || []));
const sourceValues = propsByVariant.map((props) => {
const source = deref((props as Record<string, JsonSchema>)[discriminator] || {}, new Set());
if (typeof source.const === "string") return source.const;
if (Array.isArray(source.enum) && source.enum.length === 1 && typeof source.enum[0] === "string") {
return source.enum[0] as string;
}
return null;
});
if (sourceValues.some((value) => value === null)) return null;
const uniqueSourceValues = Array.from(new Set(sourceValues as string[]));
if (uniqueSourceValues.length !== sourceValues.length) return null;
const keySets = propsByVariant.map((props) =>
Object.keys(props as Record<string, JsonSchema>).sort().join("\n")
);
if (new Set(keySets).size === 1) return null;
const merged: Record<string, JsonSchema[]> = {};
for (const props of propsByVariant as Record<string, JsonSchema>[]) {
for (const [key, value] of Object.entries(props)) {
if (!merged[key]) merged[key] = [];
merged[key].push(value);
}
}
const dedupeSchemas = (schemas: JsonSchema[]): JsonSchema[] => {
const byKey: Record<string, JsonSchema> = {};
for (const schema of schemas) {
byKey[JSON.stringify(deref(schema, new Set()))] = schema;
}
return Object.values(byKey);
};
const nextIndent = `${indent} `;
const keys = Object.keys(merged).sort((a, b) => {
if (a === discriminator) return -1;
if (b === discriminator) return 1;
return a.localeCompare(b);
});
const inner = keys
.map((key) => {
if (key === discriminator) {
return renderOption(key, { enum: uniqueSourceValues }, true, nextIndent);
}
const schemas = dedupeSchemas(merged[key]);
const schema = schemas.length === 1 ? schemas[0] : { anyOf: schemas };
const required =
propsByVariant.every((props) => key in (props as Record<string, JsonSchema>)) &&
requiredByVariant.every((requiredKeys) => requiredKeys.has(key));
return renderOption(key, schema, required, nextIndent);
})
.join("\n");
return `t.submodule { options = {\n${inner}\n${indent}}; }`;
};
const allowsPluginChannelConfigs = (pathSegments: string[]): boolean =>
pathSegments.length === 1 && pathSegments[0] === "channels";
const objectTypeForSchema = (schema: JsonSchema, indent: string, pathSegments: string[]): string => {
const properties = (schema.properties as Record<string, JsonSchema>) || {};
const requiredList = new Set((schema.required as string[]) || []);
const keys = Object.keys(properties);
@ -216,18 +286,29 @@ const objectTypeForSchema = (schema: JsonSchema, indent: string): string => {
const nextIndent = `${indent} `;
const inner = keys
.sort()
.map((key) => renderOption(key, properties[key], requiredList.has(key), nextIndent))
.map((key) =>
renderOption(key, properties[key], requiredList.has(key), nextIndent, [...pathSegments, key])
)
.join("\n");
const freeform = allowsPluginChannelConfigs(pathSegments)
? " freeformType = t.attrsOf t.anything;"
: "";
return `t.submodule { options = {\n${inner}\n${indent}}; }`;
return `t.submodule {${freeform} options = {\n${inner}\n${indent}}; }`;
};
const renderOption = (key: string, schemaObj: JsonSchema, required: boolean, indent: string): string => {
const renderOption = (
key: string,
schemaObj: JsonSchema,
required: boolean,
indent: string,
pathSegments: string[] = [key]
): string => {
const schema = deref(schemaObj, new Set());
const description = typeof schema.description === "string" ? schema.description : null;
const hasSchemaDefault = schema.default !== undefined;
const effectiveRequired = required && !hasSchemaDefault;
const baseTypeExpr = typeForSchema(schema, indent);
const baseTypeExpr = typeForSchema(schema, indent, pathSegments);
const typeExpr =
!effectiveRequired && !baseTypeExpr.startsWith("t.nullOr")
? `t.nullOr (${baseTypeExpr})`
@ -255,7 +336,11 @@ const renderOption = (key: string, schemaObj: JsonSchema, required: boolean, ind
.map((key) => renderOption(key, rootProps[key], requiredRoot.has(key), " "))
.join("\n\n");
const output = `# Generated from upstream OpenClaw schema. DO NOT EDIT.\n{ lib }:\nlet\n t = lib.types;\nin\n{\n${body}\n}\n`;
const header = schemaRev
? `# Generated from upstream OpenClaw schema at rev ${schemaRev}. DO NOT EDIT.`
: "# Generated from upstream OpenClaw schema. DO NOT EDIT.";
const output = `${header}\n# Generator: nix/scripts/generate-config-options.ts\n{ lib }:\nlet\n t = lib.types;\nin\n{\n${body}\n}\n`;
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, output, "utf8");

View File

@ -0,0 +1,72 @@
#!/bin/sh
set -e
spec="${OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC:?OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC is required}"
id="${OPENCLAW_RUNTIME_PLUGIN_ID:?OPENCLAW_RUNTIME_PLUGIN_ID is required}"
package_name="$(
node -e '
const spec = process.env.OPENCLAW_RUNTIME_PLUGIN_NPM_SPEC || "";
const withoutProtocol = spec.startsWith("npm:") ? spec.slice(4) : spec;
const at = withoutProtocol.startsWith("@")
? withoutProtocol.indexOf("@", 1)
: withoutProtocol.indexOf("@");
const name = at === -1 ? withoutProtocol : withoutProtocol.slice(0, at);
if (!name || name.startsWith("git+") || name.includes("://")) {
process.exit(1);
}
process.stdout.write(name);
'
)" || {
echo "Only registry npm package specs are supported for OpenClaw runtime plugins: $spec" >&2
exit 1
}
export HOME="$TMPDIR/home"
export npm_config_cache="$TMPDIR/npm-cache"
export npm_config_ignore_scripts=true
export npm_config_audit=false
export npm_config_fund=false
export npm_config_update_notifier=false
project="$TMPDIR/openclaw-runtime-plugin"
mkdir -p "$HOME" "$npm_config_cache" "$project"
cd "$project"
npm init -y >/dev/null
npm install --ignore-scripts --omit=dev --no-audit --no-fund --package-lock=false "$spec"
package_dir="node_modules/$package_name"
if [ ! -d "$package_dir" ]; then
echo "npm install did not produce $package_dir for $spec" >&2
exit 1
fi
if [ ! -f "$package_dir/openclaw.plugin.json" ] && [ ! -f "$package_dir/package.json" ]; then
echo "npm package $spec does not look like an OpenClaw runtime plugin" >&2
exit 1
fi
mkdir -p "$out"
cp -R "$package_dir/." "$out/"
if [ -d node_modules ]; then
mkdir -p "$out/node_modules"
cp -R node_modules/. "$out/node_modules/"
rm -rf "$out/node_modules/$package_name"
fi
find "$out" -name .package-lock.json -type f -delete
if [ ! -f "$out/openclaw.plugin.json" ]; then
node -e '
const fs = require("fs");
const path = require("path");
const pkg = JSON.parse(fs.readFileSync(path.join(process.env.out, "package.json"), "utf8"));
const entries = pkg.openclaw?.runtimeExtensions || pkg.openclaw?.extensions || [];
if (!Array.isArray(entries) || entries.length === 0) process.exit(1);
'
fi
printf '%s\n' "$spec" > "$out/.nix-openclaw-npm-spec"
printf '%s\n' "$id" > "$out/.nix-openclaw-plugin-id"

View File

@ -0,0 +1,56 @@
#!/bin/sh
set -eu
if [ -z "${OPENCLAW_GATEWAY_BIN:-}" ]; then
echo "OPENCLAW_GATEWAY_BIN is not set" >&2
exit 1
fi
if [ ! -x "$OPENCLAW_GATEWAY_BIN" ]; then
echo "OPENCLAW_GATEWAY_BIN is not executable: $OPENCLAW_GATEWAY_BIN" >&2
exit 1
fi
if [ -z "${OPENCLAW_PINNED_WRITE_PYTHON:-}" ]; then
echo "OPENCLAW_PINNED_WRITE_PYTHON is not set" >&2
exit 1
fi
if [ ! -x "$OPENCLAW_PINNED_WRITE_PYTHON" ]; then
echo "OPENCLAW_PINNED_WRITE_PYTHON is not executable: $OPENCLAW_PINNED_WRITE_PYTHON" >&2
exit 1
fi
if [ -z "${STDENV_SETUP:-}" ]; then
echo "STDENV_SETUP is not set" >&2
exit 1
fi
if [ ! -f "$STDENV_SETUP" ]; then
echo "STDENV_SETUP not found: $STDENV_SETUP" >&2
exit 1
fi
mkdir -p "$out/bin"
if [ -n "${OPENCLAW_TOOLS_PATH:-}" ]; then
bash -e -c '. "$STDENV_SETUP"; makeWrapper "$OPENCLAW_GATEWAY_BIN" "$out/bin/openclaw" --set OPENCLAW_PINNED_WRITE_PYTHON "$OPENCLAW_PINNED_WRITE_PYTHON" --prefix PATH : "$OPENCLAW_TOOLS_PATH"'
else
bash -e -c '. "$STDENV_SETUP"; makeWrapper "$OPENCLAW_GATEWAY_BIN" "$out/bin/openclaw" --set OPENCLAW_PINNED_WRITE_PYTHON "$OPENCLAW_PINNED_WRITE_PYTHON"'
fi
if [ -n "${OPENCLAW_APP_PACKAGE:-}" ]; then
app_dir="${OPENCLAW_APP_PACKAGE}/Applications"
if [ ! -d "$app_dir" ]; then
echo "OpenClaw app package has no Applications directory: $OPENCLAW_APP_PACKAGE" >&2
exit 1
fi
mkdir -p "$out/Applications"
found_app=0
for app in "$app_dir"/*.app; do
[ -e "$app" ] || continue
ln -s "$app" "$out/Applications/$(basename "$app")"
found_app=1
done
if [ "$found_app" -ne 1 ]; then
echo "OpenClaw app package has no .app under: $app_dir" >&2
exit 1
fi
fi

View File

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
qmd="${OPENCLAW_QMD_BIN:?OPENCLAW_QMD_BIN is required}"
tmp_dir="$(mktemp -d)"
cleanup() {
rm -rf "$tmp_dir"
"$qmd" collection remove openclaw-prewarm >/dev/null 2>&1 || true
}
trap cleanup EXIT
printf "%s\n\n%s\n" \
"# OpenClaw QMD prewarm" \
"This temporary document warms QMD model caches." \
> "$tmp_dir/prewarm.md"
"$qmd" collection remove openclaw-prewarm >/dev/null 2>&1 || true
"$qmd" collection add "$tmp_dir" --name openclaw-prewarm >/dev/null
"$qmd" update >/dev/null
"$qmd" embed >/dev/null
"$qmd" query "OpenClaw QMD prewarm" -n 1 --json >/dev/null

View File

@ -0,0 +1,113 @@
#!/bin/sh
set -e
log_step() {
if [ "${OPENCLAW_NIX_TIMINGS:-1}" != "1" ]; then
"$@"
return
fi
name="$1"
shift
start=$(date +%s)
printf '>> [timing] %s...\n' "$name" >&2
"$@"
end=$(date +%s)
printf '>> [timing] %s: %ss\n' "$name" "$((end - start))" >&2
}
if [ -z "${GATEWAY_PREBUILD_SH:-}" ]; then
echo "GATEWAY_PREBUILD_SH is not set" >&2
exit 1
fi
. "$GATEWAY_PREBUILD_SH"
store_path_file="${PNPM_STORE_PATH_FILE:-.pnpm-store-path}"
if [ ! -f "$store_path_file" ]; then
echo "pnpm store path file missing: $store_path_file" >&2
exit 1
fi
store_path="$(cat "$store_path_file")"
export PNPM_STORE_DIR="$store_path"
export PNPM_STORE_PATH="$store_path"
export NPM_CONFIG_STORE_DIR="$store_path"
export NPM_CONFIG_STORE_PATH="$store_path"
export HOME="$(mktemp -d)"
log_step "pnpm install (source checks)" pnpm install --offline --frozen-lockfile --ignore-scripts --prod=false --store-dir "$store_path"
ensure_root_package_link() {
pkg="$1"
root_path="node_modules/$pkg"
if [ -e "$root_path" ]; then
return 0
fi
pkg_dir="$(find node_modules/.pnpm -path "*/node_modules/$pkg" -type d | head -n 1)"
if [ -z "$pkg_dir" ]; then
return 0
fi
mkdir -p "$(dirname "$root_path")"
ln -s "$pkg_dir" "$root_path"
}
ensure_root_bin_link() {
bin_name="$1"
target_rel="$2"
bin_path="node_modules/.bin/$bin_name"
mkdir -p "$(dirname "$bin_path")"
rm -f "$bin_path"
ln -s "$target_rel" "$bin_path"
}
ensure_root_package_link "tsdown"
ensure_root_package_link "tsx"
ensure_root_bin_link "tsdown" "../tsdown/dist/run.mjs"
ensure_root_bin_link "tsx" "../tsx/dist/cli.mjs"
tsdown_cli="node_modules/tsdown/dist/run.mjs"
if [ ! -f "$tsdown_cli" ]; then
tsdown_cli="$(find node_modules -path '*/tsdown/dist/run.mjs' -type f | head -n 1)"
fi
if [ -z "${tsdown_cli:-}" ] || [ ! -f "$tsdown_cli" ]; then
echo "tsdown CLI not found under ./node_modules" >&2
exit 1
fi
tsc_cli="node_modules/typescript/bin/tsc"
if [ ! -f "$tsc_cli" ]; then
tsc_cli="$(find node_modules -path '*/typescript/bin/tsc' -type f | head -n 1)"
fi
if [ -z "${tsc_cli:-}" ] || [ ! -f "$tsc_cli" ]; then
echo "TypeScript CLI not found under ./node_modules" >&2
exit 1
fi
if [ -z "${STDENV_SETUP:-}" ]; then
echo "STDENV_SETUP is not set" >&2
exit 1
fi
if [ ! -f "$STDENV_SETUP" ]; then
echo "STDENV_SETUP not found: $STDENV_SETUP" >&2
exit 1
fi
log_step "patchShebangs node_modules/.bin" bash -e -c ". \"$STDENV_SETUP\"; patchShebangs node_modules/.bin"
log_step "node $tsdown_cli" node "$tsdown_cli" --config-loader unrun --logLevel warn
log_step "node scripts/build-stamp.mjs" node scripts/build-stamp.mjs
log_step "node $tsc_cli" node "$tsc_cli" -p tsconfig.plugin-sdk.dts.json
log_step "node --import tsx scripts/write-plugin-sdk-entry-dts.ts" node --import tsx scripts/write-plugin-sdk-entry-dts.ts
if [ -f "scripts/copy-plugin-sdk-root-alias.mjs" ]; then
log_step "node scripts/copy-plugin-sdk-root-alias.mjs" node scripts/copy-plugin-sdk-root-alias.mjs
fi
if [ -f "scripts/copy-bundled-plugin-metadata.mjs" ]; then
log_step "node scripts/copy-bundled-plugin-metadata.mjs" node scripts/copy-bundled-plugin-metadata.mjs
fi
log_step "node scripts/check-plugin-sdk-exports.mjs" node scripts/check-plugin-sdk-exports.mjs

View File

@ -0,0 +1,19 @@
#!/bin/sh
set -e
if [ -z "${CONFIG_OPTIONS_CHECK_SH:-}" ]; then
echo "CONFIG_OPTIONS_CHECK_SH is not set" >&2
exit 1
fi
if [ ! -f "$CONFIG_OPTIONS_CHECK_SH" ]; then
echo "CONFIG_OPTIONS_CHECK_SH not found: $CONFIG_OPTIONS_CHECK_SH" >&2
exit 1
fi
if [ -n "${OPENCLAW_BUILD_ROOT_SH:-}" ]; then
. "$OPENCLAW_BUILD_ROOT_SH"
openclaw_enter_build_root
trap openclaw_cleanup_output_build_root EXIT
fi
"$CONFIG_OPTIONS_CHECK_SH"

View File

@ -0,0 +1,17 @@
{
owner = "openclaw";
repo = "openclaw";
releaseVersion = "2026.5.7-dogfood.20260508";
rev = "954d20ece2de0fba3688f7800613183fbeb9685c";
hash = "sha256-6CZWsH8dV6XZ4JeG5ItKLqGAOFqbzWosyCmMXVc+c/g=";
pnpmDepsHash = "sha256-hNZA1OEuJgtoLz2hWLPk8Hm+7heLvhiZpDdBBQ1UXpc=";
fsSafeSource = {
owner = "openclaw";
repo = "fs-safe";
rev = "c7ccb99d3058f2acf2ad2758ad2470c7e113a53c";
hash = "sha256-jndOOSSFROyrK4RiwAsJfUuCJTj7qbmmm4Qz8BqtJ/c=";
};
publicSurfaceHardlinksPatch = ../patches/allow-package-public-surface-hardlinks-open-root.patch;
applySkipPluginAutoEnableNixModePatch = false;
}

View File

@ -2,7 +2,9 @@
{
owner = "openclaw";
repo = "openclaw";
rev = "222b2d7c3c6174ee31a17fbc0668acf5f1dc5e08";
hash = "sha256-UTTL/JbsEsV5ErOvDQPwika55bPwpRXQ6coO9PSWgAQ=";
pnpmDepsHash = "sha256-oYQ8hPXhwPav5vQ9VL0mfEcGPA/MyXqVisL95c3nLbc=";
releaseTag = "v2026.5.7";
releaseVersion = "2026.5.7";
rev = "eeef4864494f859838fec1586bedbab1f8fa5702";
hash = "sha256-ICkq6YfMJVvRC93sM+7/q2JI82wUhjaYAI3pRzmTHYc=";
pnpmDepsHash = "sha256-LXaRfZ0WY8VDpDc2zFr+Oel6AuYo6SiTrp37yokT1VU=";
}

View File

@ -0,0 +1,3 @@
# Test Agent
Home Manager activation fixture.

View File

@ -0,0 +1,3 @@
# Test Soul
Home Manager activation fixture.

View File

@ -0,0 +1,3 @@
# Test Tools
Home Manager activation fixture.

View File

@ -2,51 +2,64 @@
description = "nix-openclaw macOS Home Manager activation test";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
nix-openclaw.url = "github:openclaw/nix-openclaw";
nixpkgs.follows = "nix-openclaw/nixpkgs";
home-manager.follows = "nix-openclaw/home-manager";
};
outputs = { nixpkgs, home-manager, nix-openclaw, ... }:
outputs =
{
nixpkgs,
home-manager,
nix-openclaw,
...
}:
let
system = "aarch64-darwin";
pkgs = import nixpkgs {
inherit system;
overlays = [ nix-openclaw.overlays.default ];
};
in {
in
{
homeConfigurations.hm-test = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
modules = [
nix-openclaw.homeManagerModules.openclaw
({ ... }: {
home = {
username = "runner";
homeDirectory = "/tmp/hm-activation-home";
stateVersion = "23.11";
};
(
{ ... }:
{
home = {
username = "runner";
homeDirectory = "/tmp/hm-activation-home";
stateVersion = "23.11";
};
programs.openclaw = {
enable = true;
installApp = false;
instances.default = {
gatewayPort = 18999;
config = {
logging = {
level = "debug";
file = "/tmp/openclaw/openclaw-gateway.log";
};
gateway = {
mode = "local";
auth = {
token = "hm-activation-test-token";
programs.openclaw = {
enable = true;
installApp = false;
runtimePackages = [ pkgs.jq ];
environment.OPENCLAW_TEST_SECRET = "/tmp/openclaw-secret";
instances.default = {
gatewayPort = 18999;
logPath = "/tmp/hm-activation-home/.openclaw/openclaw-gateway.log";
launchd.label = "com.steipete.openclaw.gateway.hm-test";
config = {
logging = {
level = "debug";
file = "/tmp/hm-activation-home/.openclaw/openclaw-gateway.log";
};
gateway = {
mode = "local";
auth = {
token = "hm-activation-test-token";
};
};
};
};
};
};
})
}
)
];
};
};

View File

@ -5,6 +5,13 @@ machine.wait_until_succeeds(
)
machine.wait_until_succeeds("test -f /home/alice/.openclaw/openclaw.json")
machine.wait_until_succeeds("test -f /home/alice/.openclaw/workspace/AGENTS.md")
machine.succeed("test ! -L /home/alice/.openclaw/workspace/AGENTS.md")
machine.wait_until_succeeds("test -f /home/alice/.openclaw/workspace/skills/skill/SKILL.md")
machine.succeed("test ! -L /home/alice/.openclaw/workspace/skills/skill")
machine.wait_until_succeeds(
"test -x /home/alice/.openclaw/agents/main/agent/codex-home/home/.nix-profile/bin/jq"
)
uid = machine.succeed("id -u alice").strip()
machine.succeed("loginctl enable-linger alice")

View File

@ -0,0 +1,12 @@
{
outputs =
{ self }:
{
openclawPlugin = {
name = "alpha";
skills = [ ./skill ];
packages = [ ];
needs = { };
};
};
}

View File

@ -0,0 +1,6 @@
---
name: skill
description: Test OpenClaw plugin skill.
---
Test skill fixture.

View File

@ -0,0 +1,12 @@
{
outputs =
{ self }:
{
openclawPlugin = {
name = "beta";
skills = [ ./skill ];
packages = [ ];
needs = { };
};
};
}

View File

@ -0,0 +1,6 @@
---
name: skill
description: Test OpenClaw plugin skill.
---
Test skill fixture.

View File

@ -0,0 +1,4 @@
{
"id": "runtime-disabled",
"name": "Runtime Disabled"
}

View File

@ -0,0 +1,23 @@
{
outputs =
{ self }:
{
openclawPlugin = {
name = "runtime";
skills = [ ];
packages = [ ];
needs = { };
plugins = [
{
id = "runtime-test";
path = "${self.outPath}/plugin";
}
{
id = "runtime-disabled";
path = "${self.outPath}/disabled-plugin";
enabled = false;
}
];
};
};
}

View File

@ -0,0 +1,3 @@
export default {
activate() {}
};

View File

@ -0,0 +1,7 @@
{
"id": "runtime-test",
"name": "Runtime Test",
"activation": {
"onStartup": true
}
}

View File

@ -1,20 +1,27 @@
{ pkgs
, steipetePkgs ? {}
, toolNamesOverride ? null
, excludeToolNames ? []
{
pkgs,
openclawToolPkgs ? { },
toolNamesOverride ? null,
excludeToolNames ? [ ],
}:
let
lib = pkgs.lib;
safe = list: builtins.filter (p: p != null) list;
pickFrom = scope: name:
pickFrom =
scope: name:
if builtins.hasAttr name scope then
let pkg = scope.${name}; in
let
pkg = scope.${name};
in
if lib.meta.availableOn pkgs.stdenv.hostPlatform pkg then pkg else null
else
null;
pick = name:
let fromSteipete = pickFrom steipetePkgs name; in
if fromSteipete != null then fromSteipete else pickFrom pkgs name;
pick =
name:
let
fromOpenClawTools = pickFrom openclawToolPkgs name;
in
if fromOpenClawTools != null then fromOpenClawTools else pickFrom pkgs name;
ensure = names: safe (map pick names);
baseNames = [
@ -30,32 +37,17 @@ let
];
extraNames = [
"go"
"uv"
"openai-whisper"
"spotify-player"
"gogcli"
"peekaboo"
"camsnap"
"bird"
"sag"
"summarize"
"openhue-cli"
"wacli"
"sonoscli"
"ordercli"
"blucli"
"eightctl"
"mcporter"
"oracle"
"qmd"
"nano-pdf"
"goplaces"
"summarize"
"camsnap"
"sonoscli"
];
toolNamesBase = if toolNamesOverride != null then toolNamesOverride else baseNames ++ extraNames;
toolNames = builtins.filter (name: !builtins.elem name excludeToolNames) toolNamesBase;
in {
in
{
tools = ensure toolNames;
toolNames = toolNames;
}

View File

@ -1,6 +1,7 @@
# Allowed GitHub inputs in flake.lock (owner/repo)
NixOS/nixpkgs
openclaw/nix-steipete-tools
openclaw/nix-openclaw-tools
nix-community/home-manager
nix-systems/default
numtide/flake-utils
tobi/qmd

View File

@ -4,12 +4,25 @@ set -euo pipefail
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
test_dir="$repo_root/nix/tests/hm-activation-macos"
home_dir="/tmp/hm-activation-home"
label="com.steipete.openclaw.gateway.hm-test"
plist="$home_dir/Library/LaunchAgents/$label.plist"
cleanup() {
if command -v launchctl >/dev/null 2>&1; then
launchctl bootout "gui/$UID/$label" >/dev/null 2>&1 || true
if [ -e "$plist" ]; then
launchctl bootout "gui/$UID" "$plist" >/dev/null 2>&1 || true
fi
fi
}
trap cleanup EXIT
rm -rf "$home_dir"
mkdir -p "$home_dir"
cleanup
export HOME="$home_dir"
export USER="${USER:-runner}"
export USER="runner"
export LOGNAME="$USER"
cd "$test_dir"
@ -21,7 +34,43 @@ nix build --accept-flake-config --impure \
./result/activate
test -f "$HOME/.openclaw/openclaw.json"
test -f "$plist"
test -L "$HOME/.openclaw/agents/main/agent/codex-home/home/.nix-profile/bin"
test -x "$HOME/.openclaw/agents/main/agent/codex-home/home/.nix-profile/bin/jq"
if command -v launchctl >/dev/null 2>&1; then
launchctl print "gui/$UID/com.steipete.openclaw.gateway" >/dev/null 2>&1
state_file="$home_dir/launchd-state.txt"
running=false
for _ in {1..20}; do
if launchctl print "gui/$UID/$label" >"$state_file" 2>&1 && grep -q "state = running" "$state_file"; then
running=true
break
fi
sleep 0.5
done
if [ "$running" != true ]; then
cat "$state_file" >&2
exit 1
fi
openclaw_bin=$(/usr/libexec/PlistBuddy -c "Print :ProgramArguments:0" "$plist")
grep -q OPENCLAW_TEST_SECRET "$openclaw_bin"
health_file="$home_dir/gateway-health.json"
healthy=false
for _ in {1..30}; do
if "$openclaw_bin" gateway health \
--url "ws://127.0.0.1:18999" \
--token "hm-activation-test-token" \
--json \
--timeout 3000 >"$health_file" 2>&1 \
&& grep -q '"ok"[[:space:]]*:[[:space:]]*true' "$health_file"; then
healthy=true
break
fi
sleep 0.5
done
if [ "$healthy" != true ]; then
cat "$health_file" >&2
exit 1
fi
fi

View File

@ -0,0 +1,100 @@
#!/usr/bin/env node
import { pathToFileURL } from "node:url";
export function selectOpenClawRelease(releases) {
if (!Array.isArray(releases)) {
throw new Error("Expected a GitHub releases JSON array");
}
const stableReleases = releases.filter((release) => {
return release && release.draft !== true && release.prerelease !== true;
});
const latestStable = stableReleases[0] ?? null;
const latestStableSource = latestStable
? stableSourceSelection(latestStable)
: null;
const appLagStableReleases = [];
for (const release of stableReleases) {
const tagName = release.tag_name ?? release.tagName;
if (!tagName) {
appLagStableReleases.push({
tagName: null,
reason: "missing-tag",
});
continue;
}
const appAsset = (release.assets ?? []).find((asset) => {
const name = asset?.name;
return (
typeof name === "string" &&
/^OpenClaw-.*\.zip$/.test(name) &&
!/dSYM/i.test(name) &&
Boolean(asset?.browser_download_url)
);
});
if (!appAsset) {
appLagStableReleases.push({
tagName,
reason: "missing-macos-zip",
});
continue;
}
return {
latestStable: latestStableSource
? { tagName: latestStableSource.tagName }
: null,
latestStableSource,
latestMacAppStable: {
tagName,
releaseVersion: tagName.replace(/^v/, ""),
appAssetName: appAsset.name,
appUrl: appAsset.browser_download_url,
},
appLagStableReleases,
};
}
return {
latestStable: latestStableSource
? { tagName: latestStableSource.tagName }
: null,
latestStableSource,
latestMacAppStable: null,
appLagStableReleases,
};
}
function stableSourceSelection(release) {
const tagName = release.tag_name ?? release.tagName;
if (!tagName) {
return null;
}
return {
tagName,
releaseVersion: tagName.replace(/^v/, ""),
};
}
function readStdin() {
return new Promise((resolve, reject) => {
let data = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
data += chunk;
});
process.stdin.on("end", () => resolve(data));
process.stdin.on("error", reject);
});
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const input = await readStdin();
const releases = JSON.parse(input);
const selection = selectOpenClawRelease(releases);
process.stdout.write(`${JSON.stringify(selection, null, 2)}\n`);
}

View File

@ -0,0 +1,87 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { selectOpenClawRelease } from "./select-openclaw-release.mjs";
const releases = [
{
tag_name: "v2026.5.3-1",
draft: false,
prerelease: false,
assets: [],
},
{
tag_name: "v2026.5.3",
draft: false,
prerelease: false,
assets: [],
},
{
tag_name: "v2026.5.2-beta.1",
draft: false,
prerelease: true,
assets: [
{
name: "OpenClaw-2026.5.2-beta.1.zip",
browser_download_url:
"https://github.com/openclaw/openclaw/releases/download/v2026.5.2-beta.1/OpenClaw-2026.5.2-beta.1.zip",
},
],
},
{
tag_name: "v2026.5.2",
draft: false,
prerelease: false,
assets: [
{
name: "OpenClaw-2026.5.2.dmg",
browser_download_url:
"https://github.com/openclaw/openclaw/releases/download/v2026.5.2/OpenClaw-2026.5.2.dmg",
},
{
name: "OpenClaw-2026.5.2.dSYM.zip",
browser_download_url:
"https://github.com/openclaw/openclaw/releases/download/v2026.5.2/OpenClaw-2026.5.2.dSYM.zip",
},
{
name: "OpenClaw-2026.5.2.zip",
browser_download_url:
"https://github.com/openclaw/openclaw/releases/download/v2026.5.2/OpenClaw-2026.5.2.zip",
},
],
},
];
const selection = selectOpenClawRelease(releases);
assert.equal(selection.latestStable.tagName, "v2026.5.3-1");
assert.equal(selection.latestStableSource.tagName, "v2026.5.3-1");
assert.equal(selection.latestStableSource.releaseVersion, "2026.5.3-1");
assert.equal(selection.latestMacAppStable.tagName, "v2026.5.2");
assert.equal(selection.latestMacAppStable.releaseVersion, "2026.5.2");
assert.equal(
selection.latestMacAppStable.appUrl,
"https://github.com/openclaw/openclaw/releases/download/v2026.5.2/OpenClaw-2026.5.2.zip",
);
assert.deepEqual(
selection.appLagStableReleases.map((release) => release.tagName),
["v2026.5.3-1", "v2026.5.3"],
);
const none = selectOpenClawRelease([
{
tag_name: "v2026.5.3",
draft: false,
prerelease: false,
assets: [],
},
]);
assert.equal(none.latestStable.tagName, "v2026.5.3");
assert.equal(none.latestStableSource.tagName, "v2026.5.3");
assert.equal(none.latestStableSource.releaseVersion, "2026.5.3");
assert.equal(none.latestMacAppStable, null);
assert.deepEqual(none.appLagStableReleases, [
{ tagName: "v2026.5.3", reason: "missing-macos-zip" },
]);
console.log("release selection: ok");

View File

@ -9,260 +9,305 @@ fi
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
source_file="$repo_root/nix/sources/openclaw-source.nix"
app_file="$repo_root/nix/packages/openclaw-app.nix"
config_options_file="$repo_root/nix/generated/openclaw-config-options.nix"
log() {
printf '>> %s\n' "$*"
printf '>> %s\n' "$*" >&2
}
upstream_checks_green() {
local sha="$1"
local checks_json
checks_json=$(gh api "/repos/openclaw/openclaw/commits/${sha}/check-runs?per_page=100" 2>/dev/null || true)
if [[ -z "$checks_json" ]]; then
log "No check runs found for $sha"
return 1
fi
local relevant_count
relevant_count=$(printf '%s' "$checks_json" | jq '[.check_runs[] | select(.name | test("windows"; "i") | not)] | length')
if [[ "$relevant_count" -eq 0 ]]; then
log "No non-windows check runs found for $sha"
return 1
fi
local failing_count
failing_count=$(
printf '%s' "$checks_json" | jq '[.check_runs[]
| select(.name | test("windows"; "i") | not)
| select(.status != "completed" or (.conclusion != "success" and .conclusion != "skipped"))
] | length'
)
if [[ "$failing_count" -ne 0 ]]; then
log "Non-windows checks not green for $sha"
return 1
fi
return 0
usage() {
cat >&2 <<'EOF'
Usage:
scripts/update-pins.sh select
scripts/update-pins.sh apply <source_tag> <source_sha> <app_tag> <app_url>
EOF
}
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required but not installed." >&2
exit 1
fi
log "Updating nix-steipete-tools input"
nix flake update --update-input nix-steipete-tools --accept-flake-config
log "Resolving openclaw main SHAs"
mapfile -t candidate_shas < <(gh api /repos/openclaw/openclaw/commits?per_page=10 | jq -r '.[].sha' || true)
if [[ ${#candidate_shas[@]} -eq 0 ]]; then
latest_sha=$(git ls-remote https://github.com/openclaw/openclaw.git refs/heads/main | awk '{print $1}' || true)
if [[ -z "$latest_sha" ]]; then
echo "Failed to resolve openclaw main SHA" >&2
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "$1 is required but not installed." >&2
exit 1
fi
candidate_shas=("$latest_sha")
fi
}
selected_sha=""
selected_hash=""
selected_source_store_path=""
selected_source_url=""
current_field() {
local file="$1"
local key="$2"
awk -F'"' -v key="$key" '$0 ~ key" =" { print $2; exit }' "$file"
}
for sha in "${candidate_shas[@]}"; do
if ! upstream_checks_green "$sha"; then
continue
resolve_release_tag_sha() {
local tag="$1"
local tag_refs
tag_refs=$(git ls-remote https://github.com/openclaw/openclaw.git "refs/tags/${tag}" "refs/tags/${tag}^{}" || true)
if [[ -z "$tag_refs" ]]; then
echo ""
return 0
fi
log "Testing upstream SHA: $sha"
source_url="https://github.com/openclaw/openclaw/archive/${sha}.tar.gz"
log "Prefetching source tarball"
source_prefetch=$(
nix --extra-experimental-features "nix-command flakes" store prefetch-file --unpack --json "$source_url" 2>"/tmp/nix-prefetch-source.err" \
|| true
)
if [[ -z "$source_prefetch" ]]; then
cat "/tmp/nix-prefetch-source.err" >&2 || true
rm -f "/tmp/nix-prefetch-source.err"
echo "Failed to resolve source hash for $sha" >&2
continue
fi
rm -f "/tmp/nix-prefetch-source.err"
source_hash=$(printf '%s' "$source_prefetch" | jq -r '.hash // empty')
if [[ -z "$source_hash" ]]; then
printf '%s\n' "$source_prefetch" >&2
echo "Failed to parse source hash for $sha" >&2
continue
fi
source_store_path=$(printf '%s' "$source_prefetch" | jq -r '.path // .storePath // empty')
if [[ -z "$source_store_path" ]]; then
echo "Failed to parse source store path for $sha" >&2
continue
fi
log "Source hash: $source_hash"
perl -0pi -e "s|rev = \"[^\"]+\";|rev = \"${sha}\";|" "$source_file"
perl -0pi -e "s|hash = \"[^\"]+\";|hash = \"${source_hash}\";|" "$source_file"
# Force a fresh pnpmDepsHash recalculation for the candidate rev.
perl -0pi -e "s|pnpmDepsHash = \"[^\"]*\";|pnpmDepsHash = \"\";|" "$source_file"
local deref_sha plain_sha
deref_sha=$(printf '%s\n' "$tag_refs" | awk '/\^\{\}$/ { print $1; exit }')
if [[ -n "$deref_sha" ]]; then
printf '%s\n' "$deref_sha"
return 0
fi
plain_sha=$(printf '%s\n' "$tag_refs" | awk '!/\^\{\}$/ { print $1; exit }')
printf '%s\n' "$plain_sha"
}
prefetch_json() {
local url="$1"
nix --extra-experimental-features "nix-command flakes" store prefetch-file --unpack --json "$url"
}
prefetch_file_json() {
local url="$1"
nix --extra-experimental-features "nix-command flakes" store prefetch-file --json "$url"
}
unpacked_zip_hash() {
local url="$1"
local archive_prefetch archive_path unpack_dir app_list app_count app_path app_hash
archive_prefetch=$(prefetch_file_json "$url")
archive_path=$(printf '%s' "$archive_prefetch" | jq -r '.path // .storePath // empty')
if [[ -z "$archive_path" || ! -f "$archive_path" ]]; then
echo "Failed to prefetch app archive for $url" >&2
return 1
fi
unpack_dir=$(mktemp -d)
if ! unzip -q "$archive_path" -d "$unpack_dir"; then
rm -rf "$unpack_dir"
echo "Failed to unzip app archive: $archive_path" >&2
return 1
fi
app_list=$(find "$unpack_dir" -maxdepth 3 -type d -name '*.app' -print)
app_count=$(printf '%s\n' "$app_list" | sed '/^$/d' | wc -l | tr -d ' ')
if [[ "$app_count" != "1" ]]; then
rm -rf "$unpack_dir"
echo "Expected exactly one .app in app archive; found $app_count" >&2
return 1
fi
app_path=$(printf '%s\n' "$app_list" | sed -n '1p')
if [[ ! -d "$app_path/Contents" ]]; then
rm -rf "$unpack_dir"
echo "App archive contains an invalid app bundle: $app_path" >&2
return 1
fi
if ! app_hash=$(nix --extra-experimental-features "nix-command flakes" hash path "$unpack_dir"); then
rm -rf "$unpack_dir"
echo "Failed to hash unpacked app archive: $archive_path" >&2
return 1
fi
rm -rf "$unpack_dir"
printf '%s\n' "$app_hash"
}
refresh_pnpm_hash() {
local build_log pnpm_hash
build_log=$(mktemp)
log "Building gateway to validate pnpmDepsHash"
if ! nix build .#openclaw-gateway --accept-flake-config >"$build_log" 2>&1; then
pnpm_hash=$(grep -Eo 'got: *sha256-[A-Za-z0-9+/=]+' "$build_log" | head -n 1 | sed 's/.*got: *//' || true)
if [[ -n "$pnpm_hash" ]]; then
log "pnpmDepsHash mismatch detected: $pnpm_hash"
perl -0pi -e "s|pnpmDepsHash = \"[^\"]*\";|pnpmDepsHash = \"${pnpm_hash}\";|" "$source_file"
if ! nix build .#openclaw-gateway --accept-flake-config >"$build_log" 2>&1; then
tail -n 200 "$build_log" >&2 || true
rm -f "$build_log"
continue
fi
else
if [[ -z "$pnpm_hash" ]]; then
tail -n 200 "$build_log" >&2 || true
rm -f "$build_log"
continue
return 1
fi
log "pnpmDepsHash mismatch detected: $pnpm_hash"
perl -0pi -e "s|pnpmDepsHash = \"[^\"]*\";|pnpmDepsHash = \"${pnpm_hash}\";|" "$source_file"
nix build .#openclaw-gateway --accept-flake-config >"$build_log" 2>&1 || {
tail -n 200 "$build_log" >&2 || true
rm -f "$build_log"
return 1
}
fi
rm -f "$build_log"
selected_sha="$sha"
selected_hash="$source_hash"
selected_source_store_path="$source_store_path"
selected_source_url="$source_url"
break
done
}
if [[ -z "$selected_sha" ]]; then
echo "Failed to find a buildable upstream revision in the last ${#candidate_shas[@]} commits." >&2
exit 1
fi
log "Selected upstream SHA: $selected_sha"
regenerate_config_options() {
local selected_sha="$1"
local source_store_path="$2"
local tmp_src
tmp_src=$(mktemp -d)
log "Fetching latest release metadata"
release_json=$(gh api /repos/openclaw/openclaw/releases?per_page=20 || true)
if [[ -z "$release_json" ]]; then
echo "Failed to fetch release metadata" >&2
exit 1
fi
release_tag=$(printf '%s' "$release_json" | jq -r '[.[] | select([.assets[]?.name | (test("^OpenClaw-.*\\.zip$") and (test("dSYM") | not))] | any)][0].tag_name // empty')
if [[ -z "$release_tag" ]]; then
echo "Failed to resolve a release tag with an OpenClaw app asset" >&2
exit 1
fi
log "Latest app release tag with asset: $release_tag"
if [[ -d "$source_store_path" ]]; then
cp -R "$source_store_path" "$tmp_src/src"
elif [[ -f "$source_store_path" ]]; then
mkdir -p "$tmp_src/src"
tar -xf "$source_store_path" -C "$tmp_src/src" --strip-components=1
else
echo "Source path not found: $source_store_path" >&2
rm -rf "$tmp_src"
exit 1
fi
app_url=$(printf '%s' "$release_json" | jq -r '[.[] | select([.assets[]?.name | (test("^OpenClaw-.*\\.zip$") and (test("dSYM") | not))] | any)][0].assets[] | select(.name | (test("^OpenClaw-.*\\.zip$") and (test("dSYM") | not))) | .browser_download_url' | head -n 1 || true)
if [[ -z "$app_url" ]]; then
echo "Failed to resolve OpenClaw app asset URL from latest release" >&2
exit 1
fi
log "App asset URL: $app_url"
chmod -R u+w "$tmp_src/src"
app_prefetch=$(
nix --extra-experimental-features "nix-command flakes" store prefetch-file --unpack --json "$app_url" 2>"/tmp/nix-prefetch-app.err" \
|| true
)
if [[ -z "$app_prefetch" ]]; then
cat "/tmp/nix-prefetch-app.err" >&2 || true
rm -f "/tmp/nix-prefetch-app.err"
echo "Failed to resolve app hash" >&2
exit 1
fi
rm -f "/tmp/nix-prefetch-app.err"
app_hash=$(printf '%s' "$app_prefetch" | jq -r '.hash // empty')
if [[ -z "$app_hash" ]]; then
printf '%s\n' "$app_prefetch" >&2
echo "Failed to parse app hash" >&2
exit 1
fi
log "App hash: $app_hash"
nix shell --extra-experimental-features "nix-command flakes" nixpkgs#nodejs_22 nixpkgs#pnpm_10 -c \
bash -c "cd '$tmp_src/src' && pnpm install --frozen-lockfile --ignore-scripts"
app_version="${release_tag#v}"
perl -0pi -e "s|version = \"[^\"]+\";|version = \"${app_version}\";|" "$app_file"
perl -0pi -e "s|url = \"[^\"]+\";|url = \"${app_url}\";|" "$app_file"
perl -0pi -e "s|hash = \"[^\"]+\";|hash = \"${app_hash}\";|" "$app_file"
nix shell --extra-experimental-features "nix-command flakes" nixpkgs#nodejs_22 nixpkgs#pnpm_10 -c \
bash -c "cd '$tmp_src/src' && OPENCLAW_SCHEMA_REV='${selected_sha}' pnpm exec tsx '$repo_root/nix/scripts/generate-config-options.ts' --repo . --out '$config_options_file'"
if [[ -z "$selected_source_store_path" ]]; then
echo "Missing source path for selected upstream revision" >&2
exit 1
fi
log "Regenerating openclaw config options from upstream schema"
tmp_src=$(mktemp -d)
cleanup_tmp() {
rm -rf "$tmp_src"
}
trap cleanup_tmp EXIT
if [[ -d "$selected_source_store_path" ]]; then
cp -R "$selected_source_store_path" "$tmp_src/src"
elif [[ -f "$selected_source_store_path" ]]; then
mkdir -p "$tmp_src/src"
tar -xf "$selected_source_store_path" -C "$tmp_src/src" --strip-components=1
else
echo "Source path not found: $selected_source_store_path" >&2
exit 1
fi
chmod -R u+w "$tmp_src/src"
nix shell --extra-experimental-features "nix-command flakes" nixpkgs#nodejs_22 nixpkgs#pnpm_10 -c \
bash -c "cd '$tmp_src/src' && pnpm install --frozen-lockfile --ignore-scripts"
select_release() {
local release_json selection_json current_rev current_app_version source_tag source_version selected_sha
local app_tag app_version app_url latest_stable_tag app_lag_releases has_update
current_rev=$(current_field "$source_file" "rev")
current_app_version=$(current_field "$app_file" "version")
nix shell --extra-experimental-features "nix-command flakes" nixpkgs#nodejs_22 nixpkgs#pnpm_10 -c \
bash -c "cd '$tmp_src/src' && pnpm exec tsx '$repo_root/nix/scripts/generate-config-options.ts' --repo . --out '$repo_root/nix/generated/openclaw-config-options.nix'"
log "Fetching OpenClaw stable release metadata"
release_json=$(gh api '/repos/openclaw/openclaw/releases?per_page=100')
selection_json=$(printf '%s' "$release_json" | node "$repo_root/scripts/select-openclaw-release.mjs")
cleanup_tmp
trap - EXIT
latest_stable_tag=$(printf '%s' "$selection_json" | jq -r '.latestStableSource.tagName // empty')
source_tag=$(printf '%s' "$selection_json" | jq -r '.latestStableSource.tagName // empty')
source_version=$(printf '%s' "$selection_json" | jq -r '.latestStableSource.releaseVersion // empty')
app_tag=$(printf '%s' "$selection_json" | jq -r '.latestMacAppStable.tagName // empty')
app_version=$(printf '%s' "$selection_json" | jq -r '.latestMacAppStable.releaseVersion // empty')
app_url=$(printf '%s' "$selection_json" | jq -r '.latestMacAppStable.appUrl // empty')
app_lag_releases=$(printf '%s' "$selection_json" | jq -r '[.appLagStableReleases[]?.tagName | select(. != null)] | join(",")')
log "Building app to validate fetchzip hash"
current_system=$(nix eval --impure --raw --expr 'builtins.currentSystem' 2>/dev/null || true)
if [[ "$current_system" == *darwin* ]]; then
app_build_log=$(mktemp)
if ! nix build .#openclaw-app --accept-flake-config >"$app_build_log" 2>&1; then
app_hash_mismatch=$(grep -Eo 'got: *sha256-[A-Za-z0-9+/=]+' "$app_build_log" | head -n 1 | sed 's/.*got: *//' || true)
if [[ -n "$app_hash_mismatch" ]]; then
log "App hash mismatch detected: $app_hash_mismatch"
perl -0pi -e "s|hash = \"[^\"]+\";|hash = \"${app_hash_mismatch}\";|" "$app_file"
if ! nix build .#openclaw-app --accept-flake-config >"$app_build_log" 2>&1; then
tail -n 200 "$app_build_log" >&2 || true
rm -f "$app_build_log"
exit 1
fi
else
tail -n 200 "$app_build_log" >&2 || true
rm -f "$app_build_log"
if [[ -z "$source_tag" || -z "$source_version" ]]; then
echo "Failed to resolve an OpenClaw stable source release" >&2
if [[ -n "$latest_stable_tag" ]]; then
echo "Latest stable release: $latest_stable_tag" >&2
fi
exit 1
fi
selected_sha=$(resolve_release_tag_sha "$source_tag")
if [[ -z "$selected_sha" ]]; then
echo "Failed to resolve tag SHA for $source_tag" >&2
exit 1
fi
log "Selected latest stable source release: $source_tag ($selected_sha)"
if [[ -n "$app_tag" ]]; then
log "Selected latest public macOS app release: $app_tag"
else
log "No public macOS app asset found; preserving existing app pin"
fi
if [[ -n "$app_lag_releases" ]]; then
log "macOS app asset lags source release(s): $app_lag_releases"
fi
if [[ "$current_rev" == "$selected_sha" && ( -z "$app_version" || "$current_app_version" == "$app_version" ) ]]; then
has_update=false
else
has_update=true
fi
printf 'has_update=%s\n' "$has_update"
printf 'source_tag=%s\n' "$source_tag"
printf 'source_sha=%s\n' "$selected_sha"
printf 'source_version=%s\n' "$source_version"
printf 'app_tag=%s\n' "$app_tag"
printf 'app_url=%s\n' "$app_url"
printf 'app_version=%s\n' "$app_version"
printf 'latest_stable_tag=%s\n' "$latest_stable_tag"
printf 'app_lag_releases=%s\n' "$app_lag_releases"
}
apply_release() {
local source_tag="$1"
local selected_sha="$2"
local app_tag="$3"
local app_url="$4"
local source_version source_url source_prefetch source_hash source_store_path app_version app_hash
local backup_dir success
source_version="${source_tag#v}"
source_url="https://github.com/openclaw/openclaw/archive/${selected_sha}.tar.gz"
source_prefetch=$(prefetch_json "$source_url")
source_hash=$(printf '%s' "$source_prefetch" | jq -r '.hash // empty')
source_store_path=$(printf '%s' "$source_prefetch" | jq -r '.path // .storePath // empty')
if [[ -z "$source_hash" || -z "$source_store_path" ]]; then
echo "Failed to resolve source hash/path for $selected_sha" >&2
exit 1
fi
if [[ -n "$app_tag" || -n "$app_url" ]]; then
if [[ -z "$app_tag" || -z "$app_url" ]]; then
echo "app_tag and app_url must either both be set or both be empty" >&2
exit 1
fi
app_version="${app_tag#v}"
app_hash=$(unpacked_zip_hash "$app_url")
if [[ -z "$app_hash" ]]; then
echo "Failed to resolve app hash for $app_tag" >&2
exit 1
fi
fi
rm -f "$app_build_log"
else
log "Skipping app build on non-darwin system (${current_system:-unknown})"
fi
if git diff --quiet; then
echo "No pin changes detected."
exit 0
fi
backup_dir=$(mktemp -d)
success=0
cp "$source_file" "$backup_dir/source.nix"
cp "$app_file" "$backup_dir/app.nix"
cp "$config_options_file" "$backup_dir/config-options.nix"
log "Committing updated pins"
git add "$source_file" "$app_file" "$repo_root/nix/generated/openclaw-config-options.nix" "$repo_root/flake.lock"
git commit -F - <<'EOF'
🤖 codex: bump openclaw pins (no-issue)
cleanup_apply() {
if [[ "$success" -ne 1 ]]; then
cp "$backup_dir/source.nix" "$source_file"
cp "$backup_dir/app.nix" "$app_file"
cp "$backup_dir/config-options.nix" "$config_options_file"
fi
rm -rf "$backup_dir"
}
trap cleanup_apply RETURN
What:
- pin openclaw source to latest upstream main
- refresh macOS app pin to latest release asset
- update source and app hashes
- regenerate config options from upstream schema
perl -0pi -e 's| releaseTag = "[^"]+";\n||g; s| releaseVersion = "[^"]+";\n||g;' "$source_file"
perl -0pi -e "s|rev = \"[^\"]+\";|releaseTag = \"${source_tag}\";\n releaseVersion = \"${source_version}\";\n rev = \"${selected_sha}\";|" "$source_file"
perl -0pi -e "s|hash = \"[^\"]+\";|hash = \"${source_hash}\";|" "$source_file"
perl -0pi -e 's|pnpmDepsHash = "[^"]*";|pnpmDepsHash = "";|' "$source_file"
Why:
- keep nix-openclaw on latest upstream for yolo mode
if [[ -n "${app_version:-}" ]]; then
perl -0pi -e "s|version = \"[^\"]+\";|version = \"${app_version}\";|" "$app_file"
perl -0pi -e "s|url = \"[^\"]+\";|url = \"${app_url}\";|" "$app_file"
perl -0pi -e "s|hash = \"[^\"]+\";|hash = \"${app_hash}\";|" "$app_file"
fi
Tests:
- nix build .#openclaw-gateway --accept-flake-config
- nix build .#openclaw-app --accept-flake-config
EOF
refresh_pnpm_hash
regenerate_config_options "$selected_sha" "$source_store_path"
log "Rebasing on latest main"
git fetch origin main
git rebase origin/main
success=1
}
git push origin HEAD:main
mode="${1:-}"
case "$mode" in
select)
if [[ $# -ne 1 ]]; then
usage
exit 1
fi
require_cmd jq
require_cmd gh
require_cmd node
select_release
;;
apply)
if [[ $# -ne 5 ]]; then
usage
exit 1
fi
require_cmd jq
require_cmd nix
require_cmd perl
require_cmd unzip
require_cmd find
apply_release "$2" "$3" "$4" "$5"
;;
*)
usage
exit 1
;;
esac

View File

@ -2,18 +2,27 @@
description = "OpenClaw local";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
nix-openclaw.url = "github:openclaw/nix-openclaw";
nixpkgs.follows = "nix-openclaw/nixpkgs";
home-manager.follows = "nix-openclaw/home-manager";
};
outputs = { self, nixpkgs, home-manager, nix-openclaw }:
outputs =
{
self,
nixpkgs,
home-manager,
nix-openclaw,
}:
let
# REPLACE: aarch64-darwin (Apple Silicon), x86_64-darwin (Intel), or x86_64-linux
# REPLACE: aarch64-darwin (Apple Silicon) or x86_64-linux
system = "<system>";
pkgs = import nixpkgs { inherit system; overlays = [ nix-openclaw.overlays.default ]; };
in {
pkgs = import nixpkgs {
inherit system;
overlays = [ nix-openclaw.overlays.default ];
};
in
{
# REPLACE: <user> with your username (run `whoami`)
homeConfigurations."<user>" = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
@ -47,18 +56,14 @@
# REPLACE: your Telegram user ID (get from @userinfobot)
allowFrom = [ <allowFrom> ];
groups = {
"*" = { requireMention = true; };
"*" = {
requireMention = true;
};
};
};
};
instances.default = {
enable = true;
plugins = [
# Example plugin without config:
{ source = "github:acme/hello-world"; }
];
};
enable = true;
};
}
];