M README.md => README.md +2 -1
@@ 69,6 69,7 @@ Runnable examples: [`examples/basic`](examples/basic) and
| `codex.settings` | TOML linked as `$CODEX_HOME/agent-environments.config.toml` and selected with `--profile agent-environments` (layered over the account's `config.toml`). |
| `instructions.<name>.{text,order}` | Fragments of `CLAUDE.md` / `AGENTS.md`, sorted by `order` (default 1000), then name. |
| `skills.packages` | `mkSkill` packages to enable. Their skill dependencies are added automatically. |
+| `packages` | Packages put on every launcher's `PATH`, e.g. tools that hooks in `claude.settings` call. |
| `modules.skills.<name>.enable` | Declared by `mkSkillModule`; adds that skill. |
| `launchers.<cmd>.{agent,id,stateDir}` | A command. `id` defaults to the command name; `stateDir` defaults to `~/.local/agent-environments/<id>`; it must be absolute or start with `~/`, and is taken literally. |
| `companions.enable` | Put every launcher of the environment on the agent's `PATH` (as companions, which cannot start companions themselves) and add a generated `companions` skill listing each one with its non-interactive call. Launchers export `AGENT_ENV_LAUNCHER` (their command name). |
@@ 86,7 87,7 @@ On every start, under a lock on the state dir:
3. Refuses to start if a managed path is occupied by something it does not own,
such as a hand-written `CLAUDE.md`. Move it away (or turn it into an
`instructions` fragment) and start again.
-4. Sets `CLAUDE_CONFIG_DIR` / `CODEX_HOME`, puts the skills' runtime inputs on
+4. Sets `CLAUDE_CONFIG_DIR` / `CODEX_HOME`, puts `packages` and the skills' runtime inputs on
`PATH`, and runs the agent.
Launchers that share an `id` share a login and managed files. The most recent
M checks/default.nix => checks/default.nix +23 -0
@@ 156,6 156,29 @@ in
[ "$b" = "CLAUDE_CONFIG_DIR=$HOME/.local/agent-environments/claude-pers" ] || fail "$b"
'';
+ # Hooks in agent settings call tools that no skill provides; `packages` puts
+ # them on PATH, and an environment without them must not have them.
+ packages-on-path =
+ let
+ env = mkEnvironment {
+ inherit pkgs;
+ modules = [
+ stubs
+ {
+ codex.enable = true;
+ packages = [ pkgs.hello ];
+ launchers.codex-tools.agent = "codex";
+ }
+ ];
+ };
+ in
+ runTest "packages-on-path" ''
+ ${bare.launchers.codex-bare}/bin/codex-bare
+ grep -qxF "HELLO=no" $STUB_LOG.env || fail "hello on PATH without packages"
+ ${env.launchers.codex-tools}/bin/codex-tools
+ grep -qxF "HELLO=yes" $STUB_LOG.env || fail "packages not on PATH"
+ '';
+
# Without settings, no flags are injected and no files are managed.
no-settings = runTest "no-settings" ''
${bare.launchers.claude-work}/bin/claude-work
M modules/core.nix => modules/core.nix +7 -1
@@ 53,6 53,12 @@ in
description = "Skills built with lib.mkSkill. Their skill dependencies are added automatically.";
};
+ packages = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ description = "Packages put on the PATH of every launcher, e.g. tools that hooks in the agent settings call.";
+ };
+
# Read-only results used by the agent and launcher modules.
build = mkOption {
type = types.attrsOf types.anything;
@@ 73,7 79,7 @@ in
instructionsText =
lib.concatMapStringsSep "\n\n" (i: lib.removeSuffix "\n" i.text) sortedInstructions + "\n";
skills = lib.mapAttrsToList (_: ss: lib.head ss) skillsByName;
- skillRuntimeInputs = lib.unique (lib.concatMap (s: s.runtimeInputs) skillClosure);
+ runtimeInputs = lib.unique (config.packages ++ lib.concatMap (s: s.runtimeInputs) skillClosure);
};
assertions = lib.mapAttrsToList (name: ss: {
M modules/launcher.nix => modules/launcher.nix +1 -1
@@ 42,7 42,7 @@ let
agent = build.agents.${launcher.agent};
links = managedLinks agent;
runtimePath = lib.makeBinPath (
- lib.optional (companionBin != null) companionBin ++ build.skillRuntimeInputs
+ lib.optional (companionBin != null) companionBin ++ build.runtimeInputs
);
in
pkgs.writeShellApplication {