diff --git a/README.md b/README.md index cf2604ef7..2f4b8ac4e 100644 --- a/README.md +++ b/README.md @@ -614,7 +614,7 @@ bun setup bun dev ``` -Build and smoke-test the distributable Nix package with `nix build .#omp`. `nix/bun.nix` is generated only when `bun.lock` changes; releases regenerate it automatically. For dependency changes, run: +Build and smoke-test the distributable Nix package with `nix build .#omp`. Wayland screencast support is off by default (linking libpipewire adds ~750 MB of runtime closure); enable it with `omp.override { withWaylandScreencast = true; }`. `nix/bun.nix` is generated only when `bun.lock` changes; releases regenerate it automatically. For dependency changes, run: ```sh bun run gen:nix diff --git a/docs/advisor-watchdog.md b/docs/advisor-watchdog.md index a0c3bb85e..d80b68df0 100644 --- a/docs/advisor-watchdog.md +++ b/docs/advisor-watchdog.md @@ -297,7 +297,7 @@ Fields: Subagents run unadvised by default; advisors are opted in **per agent** instead of via a blanket toggle: - Agent definition frontmatter `advisor`: `true` advises spawned sessions of that agent with the model resolved for the `advisor` role; a string (e.g. `advisor: "deepseek/deepseek-v4-flash"` or `advisor: "@smol:high"`) sets an explicit advisor model pattern with an optional `:level` thinking suffix. -- The `task.agentAdvisor` settings record (agent name → `"on"` / `"off"` / model pattern) overrides the frontmatter, and is edited per agent from `/agents` with `A` (an inline editor with model suggestions and a live resolution preview). +- The `task.agentAdvisor` settings record (agent name → `"on"` / `"off"` / model pattern) overrides the frontmatter, and is configured per agent from the `/agents` hub: Enter on an agent opens its property strip; the advisor strip offers on/off, a model-browser pick, or a raw pattern. The legacy `advisor.subagents: true` setting migrates to `task.agentAdvisor: { task: "on" }` — the bundled generic `task` agent keeps its advisor, other agents start unadvised. diff --git a/docs/settings.md b/docs/settings.md index f21b5f833..d23c92805 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -374,7 +374,7 @@ See [Advisor and WATCHDOG.md](./advisor-watchdog.md) for runtime behavior, `WATC | Key | Type | Default | Notes | | --------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `advisor.enabled` | boolean | `false` | Enable the advisor runtime when `modelRoles.advisor` resolves to an available model. | -| `task.agentAdvisor` | record | `{}` | Per-agent subagent advisor: agent name → `"on"` / `"off"` / advisor model pattern. Overrides agent frontmatter `advisor`; edited from `/agents` with `A`. | +| `task.agentAdvisor` | record | `{}` | Per-agent subagent advisor: agent name → `"on"` / `"off"` / advisor model pattern. Overrides agent frontmatter `advisor`; configured from the `/agents` hub. | | `advisor.syncBacklog` | enum | `off` | Bounded advisor catch-up delay: `off`, `1`, `3`, or `5`. The primary waits up to 30 seconds only while advisor backlog is at or above the threshold. | | `advisor.immuneTurns` | number | `3` | After a `concern`/`blocker` interrupts, route further concerns/blockers as non-interrupting asides for this many completed primary turns. | diff --git a/docs/task-agent-discovery.md b/docs/task-agent-discovery.md index ae8adfb6d..05cff512d 100644 --- a/docs/task-agent-discovery.md +++ b/docs/task-agent-discovery.md @@ -43,8 +43,8 @@ Parsing comes from frontmatter via `parseAgentFields()` (`src/discovery/helpers. - `thinking-level` / `thinking` selects the agent's configured effort. When `task.enableEffort` (default `false`) exposes it, a task item's coarse `effort` (`lo`, `med`, `hi`) takes precedence at launch. OMP maps that hint to the selected model's lowest, middle, or highest supported effort, then clamps it to `task.maxEffort` (default `max`). The ceiling is carried across retry-fallback model switches. If the selected model has no supported effort at or below the ceiling, the spawn fails; models without a controllable effort surface instead fall back to their normal selector. - `blocking: true` makes the parent wait for that agent even when async task execution is enabled - `autoloadSkills` names skills from the parent session to inject before the first child prompt; unknown names are ignored -- `prewalk: true` starts the subagent on its resolved model and hands off to the default prewalk target (the `smol` role) at its first edit/write, exactly like the session-level `--prewalk`; a string value (e.g. `prewalk: "@smol"` or `prewalk: "openai/gpt-5-mini"`) picks a custom target. The `task.agentPrewalk` settings record (agent name → `"on"` / `"off"` / pattern, edited per agent from `/agents` with `P`) overrides the frontmatter. Resolution happens in `runSubprocess` (`src/task/executor.ts`). An unavailable target is skipped instead of failing the spawn. A resolved target is skipped only when both its model identity and its effective thinking mode/level match the starting selection after model clamping; a same-model effort downgrade is a real hand-off and still arms and switches at the first edit/write. -- `advisor: true` pairs spawned sessions of the agent with an advisor running the model resolved for the `advisor` role; a string value (e.g. `advisor: "deepseek/deepseek-v4-flash"` or `advisor: "@smol:high"`) sets an explicit advisor model pattern (optional `:level` suffix), applied as the spawned session's `modelRoles.advisor`. The `task.agentAdvisor` settings record (agent name → `"on"` / `"off"` / pattern, edited per agent from `/agents` with `A`) overrides the frontmatter. Resolution happens in `runSubprocess` (`src/task/executor.ts`); subagents default to no advisor, and the effective opt-in is persisted in `session_init` so cold revival restores it. +- `prewalk: true` starts the subagent on its resolved model and hands off to the default prewalk target (the `smol` role) at its first edit/write, exactly like the session-level `--prewalk`; a string value (e.g. `prewalk: "@smol"` or `prewalk: "openai/gpt-5-mini"`) picks a custom target. The `task.agentPrewalk` settings record (agent name → `"on"` / `"off"` / pattern, configured per agent from the `/agents` hub via its prewalk strip) overrides the frontmatter. Resolution happens in `runSubprocess` (`src/task/executor.ts`). An unavailable target is skipped instead of failing the spawn. A resolved target is skipped only when both its model identity and its effective thinking mode/level match the starting selection after model clamping; a same-model effort downgrade is a real hand-off and still arms and switches at the first edit/write. +- `advisor: true` pairs spawned sessions of the agent with an advisor running the model resolved for the `advisor` role; a string value (e.g. `advisor: "deepseek/deepseek-v4-flash"` or `advisor: "@smol:high"`) sets an explicit advisor model pattern (optional `:level` suffix), applied as the spawned session's `modelRoles.advisor`. The `task.agentAdvisor` settings record (agent name → `"on"` / `"off"` / pattern, configured per agent from the `/agents` hub via its advisor strip) overrides the frontmatter. Resolution happens in `runSubprocess` (`src/task/executor.ts`); subagents default to no advisor, and the effective opt-in is persisted in `session_init` so cold revival restores it. ## Role-backed custom agents diff --git a/nix/home-manager.nix b/nix/home-manager.nix index f449522c6..9f74df866 100644 --- a/nix/home-manager.nix +++ b/nix/home-manager.nix @@ -23,7 +23,12 @@ in settings = lib.mkOption { type = lib.types.nullOr yaml.type; default = null; - description = "Settings written declaratively to ~/.omp/agent/config.yml."; + description = '' + Settings written declaratively to {file}`~/.omp/agent/config.yml`. + The file is a read-only store symlink: changes made from inside OMP + (`/settings`, onboarding) replace it but revert on the next + `home-manager switch`. + ''; example = { theme.dark = "titanium"; startup.quiet = true; diff --git a/nix/package.nix b/nix/package.nix index d306f8c4a..11ec5b3c0 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,17 +1,13 @@ { - autoPatchelfHook ? null, - autoSignDarwinBinariesHook ? null, + autoPatchelfHook, bun, bun2nix, cmake, + darwin, lib, libopus, - libpulseaudio ? null, - makeWrapper, ninja, - openssl, - pcre2, - pipewire ? null, + pipewire, pkg-config, rustPlatform, rustToolchain, @@ -19,8 +15,10 @@ stdenv, stdenvNoCC, unzip, - zig, - zlib, + # Wayland screencast support links libpipewire, whose runtime closure adds + # ~750 MB (gstreamer, ffmpeg, systemd, ...). Official npm/Bazel addons ship + # without it, so default to the lean build; opt in via `.override`. + withWaylandScreencast ? false, }: let packageJson = lib.importJSON ../packages/coding-agent/package.json; @@ -68,142 +66,137 @@ let ''; }; in -stdenv.mkDerivation ( - { - pname = "omp"; - inherit (packageJson) version; - src = source; +stdenv.mkDerivation { + pname = "omp"; + inherit (packageJson) version; + src = source; - cargoDeps = rustPlatform.importCargoLock { lockFile = ../Cargo.lock; }; - bunDeps = bun2nix.fetchBunDeps { - bunNix = ./bun.nix; - overrides = patchOverrides; - }; + cargoDeps = rustPlatform.importCargoLock { lockFile = ../Cargo.lock; }; + bunDeps = bun2nix.fetchBunDeps { + bunNix = ./bun.nix; + overrides = patchOverrides; + }; - nativeBuildInputs = [ - bun - bun2nix.hook - cmake - makeWrapper - ninja - pkg-config - rustPlatform.bindgenHook - rustPlatform.cargoSetupHook - rustToolchain - zig - ] - ++ lib.optionals stdenv.hostPlatform.isLinux [ autoPatchelfHook ] - ++ lib.optionals stdenv.hostPlatform.isDarwin [ autoSignDarwinBinariesHook ]; + nativeBuildInputs = [ + bun + bun2nix.hook + cmake + ninja + pkg-config + rustPlatform.bindgenHook + rustPlatform.cargoSetupHook + rustToolchain + ] + ++ lib.optionals stdenv.hostPlatform.isLinux [ autoPatchelfHook ] + ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.autoSignDarwinBinariesHook ]; - buildInputs = [ - libopus - openssl - pcre2 - ] - ++ lib.optionals stdenv.hostPlatform.isLinux [ - libpulseaudio - pipewire - stdenv.cc.cc.lib - zlib - ]; + # pcre2 is vendored via PCRE2_SYS_STATIC, but opus must link the nixpkgs + # library: audiopus_sys' bundled cmake build installs to lib64 while its + # link-search hardcodes lib, so the pkg-config path is the one that works. + # libgcc_s is resolved from the compiler's lib output during autoPatchelf. + # All dynamic store paths are pinned into the closure via nix-support (see + # installPhase). + buildInputs = [ + libopus + ] + ++ lib.optionals stdenv.hostPlatform.isLinux [ stdenv.cc.cc.lib ] + ++ lib.optionals withWaylandScreencast [ pipewire ]; - strictDeps = true; - # Nix builders cannot reliably hardlink cache files into node_modules - # (and Darwin's clonefile backend also rejects store permissions). - bunInstallFlags = [ - "--linker=isolated" - "--backend=copyfile" - ]; - dontConfigure = true; - dontRunLifecycleScripts = true; - dontUseBunBuild = true; - dontUseBunCheck = true; - dontUseBunInstall = true; - dontStrip = true; + strictDeps = true; + # Nix builders cannot reliably hardlink cache files into node_modules + # (and Darwin's clonefile backend also rejects store permissions). + bunInstallFlags = [ + "--linker=isolated" + "--backend=copyfile" + ]; + dontConfigure = true; + dontRunLifecycleScripts = true; + dontUseBunBuild = true; + dontUseBunCheck = true; + dontUseBunInstall = true; + dontStrip = true; - env = { - CMAKE_POLICY_VERSION_MINIMUM = "3.5"; - PCRE2_SYS_STATIC = "1"; - SOURCE_DATE_EPOCH = "1"; + env = { + CMAKE_POLICY_VERSION_MINIMUM = "3.5"; + PCRE2_SYS_STATIC = "1"; + SOURCE_DATE_EPOCH = "1"; + } + // lib.optionalAttrs (platform ? rustFlags) { RUSTFLAGS = platform.rustFlags; } + // lib.optionalAttrs stdenv.hostPlatform.isDarwin { BUN_NO_CODESIGN_MACHO_BINARY = "1"; }; + + buildPhase = '' + runHook preBuild + + echo "Building pi-natives" + cargo build --release -p pi-natives ${lib.optionalString withWaylandScreencast "--features wayland-pipewire"} + install -Dm755 "target/release/${platform.nativeLibrary}" \ + "packages/natives/native/${platform.addon}" + ${lib.optionalString stdenv.hostPlatform.isLinux '' + # The loader extracts this archived addon at runtime, so fix its + # interpreter-independent Nix RPATH before Bun embeds it. + autoPatchelf -- "packages/natives/native/${platform.addon}" + ''} + ${lib.optionalString stdenv.hostPlatform.isDarwin '' + # arm64 Darwin requires even locally-built Mach-O addons to carry an + # ad-hoc signature. Sign before Bun archives the file. + signIfRequired "packages/natives/native/${platform.addon}" + ''} + + echo "Compiling OMP" + BUN_COMPILE_EXECUTABLE_PATH="${bunRuntimeTemplate}/libexec/bun" \ + bun --cwd="$PWD/packages/coding-agent" run build + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + install -Dm755 packages/coding-agent/dist/omp "$out/bin/omp" + + # The addon is gzip-compressed inside the compiled binary, so the store + # paths it links against are invisible to the output reference scanner. + # Record them in plain text to pin the libraries into the runtime closure. + mkdir -p "$out/nix-support" + ${ + if stdenv.hostPlatform.isLinux then + '' + patchelf --print-rpath "packages/natives/native/${platform.addon}" \ + > "$out/nix-support/embedded-addon-runpath" + '' + else + '' + echo "${lib.getLib libopus}/lib" > "$out/nix-support/embedded-addon-runpath" + '' } - // lib.optionalAttrs (platform ? rustFlags) { RUSTFLAGS = platform.rustFlags; } - // lib.optionalAttrs stdenv.hostPlatform.isDarwin { BUN_NO_CODESIGN_MACHO_BINARY = "1"; }; - buildPhase = '' - runHook preBuild + runHook postInstall + ''; - echo "Building pi-natives" - cargo build --release -p pi-natives ${lib.optionalString stdenv.hostPlatform.isLinux "--features wayland-pipewire"} - install -Dm755 "target/release/${platform.nativeLibrary}" \ - "packages/natives/native/${platform.addon}" - ${lib.optionalString stdenv.hostPlatform.isLinux '' - # The loader extracts this archived addon at runtime, so fix its - # interpreter-independent Nix RPATH before Bun embeds it. - autoPatchelf -- "packages/natives/native/${platform.addon}" - ''} - ${lib.optionalString stdenv.hostPlatform.isDarwin '' - # arm64 Darwin requires even locally-built Mach-O addons to carry an - # ad-hoc signature. Sign before Bun archives the file. - signIfRequired "packages/natives/native/${platform.addon}" - ''} + doInstallCheck = true; + installCheckPhase = '' + runHook preInstallCheck + HOME="$TMPDIR" "$out/bin/omp" --smoke-test | grep -q "smoke-test: ok" + BUN_BE_BUN=1 "$out/bin/omp" -e \ + 'if (Bun.version !== "${bun.version}" || typeof Bun.Image !== "function") process.exit(1)' + runHook postInstallCheck + ''; - echo "Compiling OMP" - BUN_COMPILE_EXECUTABLE_PATH="${bunRuntimeTemplate}/libexec/bun" \ - bun --cwd="$PWD/packages/coding-agent" run build - - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - - install -Dm755 packages/coding-agent/dist/omp "$out/libexec/omp/omp" - - ${lib.optionalString stdenv.hostPlatform.isLinux '' - # The addon is gzip-compressed inside the compiled binary, so the Nix - # store RUNPATH autoPatchelf wrote into it is invisible to the output - # reference scanner. Record it in plain text to pin those libraries - # (pipewire, libopus, libgcc) into the runtime closure. - mkdir -p "$out/nix-support" - patchelf --print-rpath "packages/natives/native/${platform.addon}" \ - > "$out/nix-support/embedded-addon-runpath" - ''} - - makeWrapper "$out/libexec/omp/omp" "$out/bin/omp" \ - --set PI_SKIP_VERSION_CHECK 1 - - runHook postInstall - ''; - - doInstallCheck = true; - installCheckPhase = '' - runHook preInstallCheck - HOME="$TMPDIR" "$out/bin/omp" --smoke-test | grep -q "smoke-test: ok" - BUN_BE_BUN=1 "$out/libexec/omp/omp" -e \ - 'if (Bun.version !== "${bun.version}" || typeof Bun.Image !== "function") process.exit(1)' - runHook postInstallCheck - ''; - - meta = { - description = "Terminal-based coding agent with multi-model support"; - homepage = "https://omp.sh"; - changelog = "https://github.com/can1357/oh-my-pi/releases/tag/v${packageJson.version}"; - license = lib.licenses.mit; - mainProgram = "omp"; - platforms = [ - "aarch64-darwin" - "aarch64-linux" - "x86_64-darwin" - "x86_64-linux" - ]; - sourceProvenance = with lib.sourceTypes; [ - binaryNativeCode - fromSource - ]; - }; - } - // lib.optionalAttrs stdenv.hostPlatform.isDarwin { - # The compile output is intentionally unsigned until all Darwin fixups are complete. - darwinDontCodeSign = false; - } -) + meta = { + description = "Terminal-based coding agent with multi-model support"; + homepage = "https://omp.sh"; + changelog = "https://github.com/can1357/oh-my-pi/releases/tag/v${packageJson.version}"; + license = lib.licenses.mit; + mainProgram = "omp"; + platforms = [ + "aarch64-darwin" + "aarch64-linux" + "x86_64-darwin" + "x86_64-linux" + ]; + sourceProvenance = with lib.sourceTypes; [ + binaryNativeCode + fromSource + ]; + }; +}