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
This commit is contained in:
parent
fd30aad492
commit
54e09bce18
@ -209,16 +209,30 @@ let
|
|||||||
qmd.prewarmModels.enable = true;
|
qmd.prewarmModels.enable = true;
|
||||||
};
|
};
|
||||||
qmdPrewarmActivation = builtins.toJSON qmdPrewarmEval.config.home.activation.openclawQmdPrewarm;
|
qmdPrewarmActivation = builtins.toJSON qmdPrewarmEval.config.home.activation.openclawQmdPrewarm;
|
||||||
qmdPrewarmCheck =
|
qmdPrewarmCheck = builtins.deepSeq (requireNoAssertionFailures "qmd.prewarmModels" qmdPrewarmEval) (
|
||||||
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
|
if
|
||||||
lib.hasInfix "OPENCLAW_QMD_BIN=" qmdPrewarmActivation
|
lib.hasInfix "openclaw-link-codex-runtime-profiles.sh" runtimeProfileActivation
|
||||||
&& lib.hasInfix "openclaw-qmd-prewarm.sh" qmdPrewarmActivation
|
|
||||||
then
|
then
|
||||||
"ok"
|
"ok"
|
||||||
else
|
else
|
||||||
throw "qmd.prewarmModels did not wire QMD model-cache prewarm activation."
|
throw "runtimePackages did not wire the Codex runtime profile activation."
|
||||||
);
|
);
|
||||||
|
|
||||||
checkKey = builtins.deepSeq [
|
checkKey = builtins.deepSeq [
|
||||||
@ -228,6 +242,7 @@ let
|
|||||||
userPluginSkillCollisionCheck
|
userPluginSkillCollisionCheck
|
||||||
secretProviderCheck
|
secretProviderCheck
|
||||||
qmdPrewarmCheck
|
qmdPrewarmCheck
|
||||||
|
runtimeProfileCheck
|
||||||
] "ok";
|
] "ok";
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|||||||
22
nix/modules/home-manager/openclaw-link-codex-runtime-profiles.sh
Executable file
22
nix/modules/home-manager/openclaw-link-codex-runtime-profiles.sh
Executable 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"
|
||||||
@ -22,6 +22,8 @@ let
|
|||||||
gatewayPort = 18789;
|
gatewayPort = 18789;
|
||||||
gatewayPath = null;
|
gatewayPath = null;
|
||||||
gatewayPnpmDepsHash = lib.fakeHash;
|
gatewayPnpmDepsHash = lib.fakeHash;
|
||||||
|
runtimePackages = [ ];
|
||||||
|
environment = { };
|
||||||
launchd = cfg.launchd;
|
launchd = cfg.launchd;
|
||||||
systemd = cfg.systemd;
|
systemd = cfg.systemd;
|
||||||
plugins = openclawLib.effectivePlugins;
|
plugins = openclawLib.effectivePlugins;
|
||||||
@ -97,7 +99,24 @@ let
|
|||||||
else
|
else
|
||||||
inst.package;
|
inst.package;
|
||||||
pluginPackages = plugins.pluginPackagesFor name;
|
pluginPackages = plugins.pluginPackagesFor name;
|
||||||
pluginEnvAll = plugins.pluginEnvAllFor 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));
|
||||||
mergedConfig0 = stripNulls (
|
mergedConfig0 = stripNulls (
|
||||||
lib.recursiveUpdate (lib.recursiveUpdate baseConfig (stripNulls cfg.config)) (
|
lib.recursiveUpdate (lib.recursiveUpdate baseConfig (stripNulls cfg.config)) (
|
||||||
stripNulls inst.config
|
stripNulls inst.config
|
||||||
@ -117,11 +136,20 @@ let
|
|||||||
mergedConfig0;
|
mergedConfig0;
|
||||||
configJson = builtins.toJSON mergedConfig;
|
configJson = builtins.toJSON mergedConfig;
|
||||||
configFile = pkgs.writeText "openclaw-${name}.json" configJson;
|
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}" ''
|
gatewayWrapper = pkgs.writeShellScriptBin "openclaw-gateway-${name}" ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [ -n "${lib.makeBinPath pluginPackages}" ]; then
|
if [ -n "${runtimePath}" ]; then
|
||||||
export PATH="${lib.makeBinPath pluginPackages}:$PATH"
|
export PATH="${runtimePath}:$PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${lib.concatStringsSep "\n" (
|
${lib.concatStringsSep "\n" (
|
||||||
@ -146,7 +174,7 @@ let
|
|||||||
export ${entry.key}="${entry.value}"
|
export ${entry.key}="${entry.value}"
|
||||||
fi
|
fi
|
||||||
''
|
''
|
||||||
) pluginEnvAll
|
) runtimeEnvAll
|
||||||
)}
|
)}
|
||||||
|
|
||||||
exec "${gatewayPackage}/bin/openclaw" "$@"
|
exec "${gatewayPackage}/bin/openclaw" "$@"
|
||||||
@ -181,6 +209,8 @@ let
|
|||||||
};
|
};
|
||||||
configFile = configFile;
|
configFile = configFile;
|
||||||
configPath = inst.configPath;
|
configPath = inst.configPath;
|
||||||
|
codexRuntimeProfiles = codexRuntimeProfiles;
|
||||||
|
runtimeProfile = runtimeProfile;
|
||||||
|
|
||||||
dirs = [
|
dirs = [
|
||||||
inst.stateDir
|
inst.stateDir
|
||||||
@ -245,6 +275,21 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
instanceConfigs = lib.mapAttrsToList mkInstanceConfig enabledInstances;
|
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);
|
appInstalls = lib.filter (item: item != null) (map (item: item.appInstall) instanceConfigs);
|
||||||
launchdLabels = lib.filter (label: label != null) (map (item: item.launchdLabel) instanceConfigs);
|
launchdLabels = lib.filter (label: label != null) (map (item: item.launchdLabel) instanceConfigs);
|
||||||
launchdLabelArgs = lib.concatStringsSep " " (map lib.escapeShellArg launchdLabels);
|
launchdLabelArgs = lib.concatStringsSep " " (map lib.escapeShellArg launchdLabels);
|
||||||
@ -318,24 +363,28 @@ in
|
|||||||
)}
|
)}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
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" ] ''
|
home.activation.openclawPluginGuard = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
${plugins.pluginGuards}
|
${plugins.pluginGuards}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
home.activation.openclawQmdPrewarm =
|
home.activation.openclawQmdPrewarm = lib.mkIf (cfg.qmd.prewarmModels.enable && qmdPackage != null) (
|
||||||
lib.mkIf (cfg.qmd.prewarmModels.enable && qmdPackage != null)
|
lib.hm.dag.entryAfter [ "openclawDirs" ] ''
|
||||||
(
|
run --quiet ${lib.getExe' pkgs.coreutils "env"} \
|
||||||
lib.hm.dag.entryAfter [ "openclawDirs" ] ''
|
HOME=${lib.escapeShellArg homeDir} \
|
||||||
run --quiet ${lib.getExe' pkgs.coreutils "env"} \
|
XDG_CACHE_HOME=${lib.escapeShellArg "${homeDir}/.cache"} \
|
||||||
HOME=${lib.escapeShellArg homeDir} \
|
XDG_CONFIG_HOME=${lib.escapeShellArg "${homeDir}/.config"} \
|
||||||
XDG_CACHE_HOME=${lib.escapeShellArg "${homeDir}/.cache"} \
|
XDG_DATA_HOME=${lib.escapeShellArg "${homeDir}/.local/share"} \
|
||||||
XDG_CONFIG_HOME=${lib.escapeShellArg "${homeDir}/.config"} \
|
OPENCLAW_QMD_BIN=${lib.escapeShellArg "${qmdPackage}/bin/qmd"} \
|
||||||
XDG_DATA_HOME=${lib.escapeShellArg "${homeDir}/.local/share"} \
|
${pkgs.bash}/bin/bash ${../../../scripts/openclaw-qmd-prewarm.sh}
|
||||||
OPENCLAW_QMD_BIN=${lib.escapeShellArg "${qmdPackage}/bin/qmd"} \
|
''
|
||||||
${pkgs.bash}/bin/bash ${../../../scripts/openclaw-qmd-prewarm.sh}
|
);
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
home.activation.openclawAppDefaults =
|
home.activation.openclawAppDefaults =
|
||||||
lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != { })
|
lib.mkIf (pkgs.stdenv.hostPlatform.isDarwin && appDefaults != { })
|
||||||
|
|||||||
@ -65,6 +65,18 @@
|
|||||||
description = "pnpmDeps hash for local gateway builds (omit to let Nix suggest the correct hash).";
|
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 {
|
plugins = lib.mkOption {
|
||||||
type = lib.types.listOf (
|
type = lib.types.listOf (
|
||||||
lib.types.submodule {
|
lib.types.submodule {
|
||||||
|
|||||||
@ -107,6 +107,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 {
|
documents = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.path;
|
type = lib.types.nullOr lib.types.path;
|
||||||
default = null;
|
default = null;
|
||||||
|
|||||||
@ -38,6 +38,8 @@
|
|||||||
programs.openclaw = {
|
programs.openclaw = {
|
||||||
enable = true;
|
enable = true;
|
||||||
installApp = false;
|
installApp = false;
|
||||||
|
runtimePackages = [ pkgs.jq ];
|
||||||
|
environment.OPENCLAW_TEST_SECRET = "/tmp/openclaw-secret";
|
||||||
instances.default = {
|
instances.default = {
|
||||||
gatewayPort = 18999;
|
gatewayPort = 18999;
|
||||||
logPath = "/tmp/hm-activation-home/.openclaw/openclaw-gateway.log";
|
logPath = "/tmp/hm-activation-home/.openclaw/openclaw-gateway.log";
|
||||||
|
|||||||
@ -9,6 +9,9 @@ machine.wait_until_succeeds("test -f /home/alice/.openclaw/workspace/AGENTS.md")
|
|||||||
machine.succeed("test ! -L /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.wait_until_succeeds("test -f /home/alice/.openclaw/workspace/skills/skill/SKILL.md")
|
||||||
machine.succeed("test ! -L /home/alice/.openclaw/workspace/skills/skill")
|
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()
|
uid = machine.succeed("id -u alice").strip()
|
||||||
machine.succeed("loginctl enable-linger alice")
|
machine.succeed("loginctl enable-linger alice")
|
||||||
|
|||||||
@ -35,6 +35,8 @@ nix build --accept-flake-config --impure \
|
|||||||
|
|
||||||
test -f "$HOME/.openclaw/openclaw.json"
|
test -f "$HOME/.openclaw/openclaw.json"
|
||||||
test -f "$plist"
|
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
|
if command -v launchctl >/dev/null 2>&1; then
|
||||||
state_file="$home_dir/launchd-state.txt"
|
state_file="$home_dir/launchd-state.txt"
|
||||||
@ -52,6 +54,7 @@ if command -v launchctl >/dev/null 2>&1; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
openclaw_bin=$(/usr/libexec/PlistBuddy -c "Print :ProgramArguments:0" "$plist")
|
openclaw_bin=$(/usr/libexec/PlistBuddy -c "Print :ProgramArguments:0" "$plist")
|
||||||
|
grep -q OPENCLAW_TEST_SECRET "$openclaw_bin"
|
||||||
health_file="$home_dir/gateway-health.json"
|
health_file="$home_dir/gateway-health.json"
|
||||||
healthy=false
|
healthy=false
|
||||||
for _ in {1..30}; do
|
for _ in {1..30}; do
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user