diff --git a/INSTALL.md b/INSTALL.md index 0daedd3d0e..4abe5ce2c5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -144,6 +144,21 @@ A checkout has no Rust PTY host until one is put in `bin/`: on a release tag, `cargo build --release --manifest-path crates/pty-sidecar/Cargo.toml` builds one. Without either, terminals run on Node 18+ where the PC has it, and are mirrored where it has none. +On Linux and macOS, a service manager may start with a smaller `PATH` than an interactive shell. +Use the existing launcher so Bun, Node and agents installed in `~/.local/bin` are discoverable. +For example, a systemd unit's `[Service]` section can use: + +```ini +WorkingDirectory=/absolute/path/to/ovis +ExecStart=/bin/sh /absolute/path/to/ovis/scripts/with-bun.sh run start +``` + +Run the service as the user who installed the agents, with that user's `HOME`. The launcher adds +the usual user-local directories after the existing `PATH`, preserving its command choices. +If an installed agent such as Grok is missing from the new-session picker, check the service's +`PATH`: the picker hides agents the server cannot find. Restart the web UI after changing the +service configuration. + ## 4. Verify ```bash diff --git a/changelog.d/source-service-agent-path.md b/changelog.d/source-service-agent-path.md new file mode 100644 index 0000000000..2080c8a698 --- /dev/null +++ b/changelog.d/source-service-agent-path.md @@ -0,0 +1,3 @@ +### Maintenance +- Document the existing launcher for source installs under a service manager, so a restricted + `PATH` does not hide user-local agents such as Grok from the new-session picker. diff --git a/scripts/with-bun.test.ts b/scripts/with-bun.test.ts index 82b1fad1b9..ea40d08100 100644 --- a/scripts/with-bun.test.ts +++ b/scripts/with-bun.test.ts @@ -62,6 +62,27 @@ describe.skipIf(process.platform === "win32")("Unix Bun discovery", () => { expect(result.out.trim()).toBe("1.4.3"); }); + it("lets the launched Bun discover Grok in ~/.local/bin with a service's bare PATH", async () => { + const bin = join(home, ".bun", "bin"); + mkdirSync(bin, { recursive: true }); + symlinkSync(process.execPath, join(bin, "bun")); + tool(join(home, ".local", "bin"), "grok", "fixture"); + const result = await run(["/bin/sh", script, "-e", 'console.log(Bun.which("grok"))']); + expect(result.code).toBe(0); + expect(result.out.trim()).toBe(join(home, ".local", "bin", "grok")); + }); + + it("keeps a Grok already on PATH ahead of the user-local fallback", async () => { + const bin = join(home, ".bun", "bin"); + mkdirSync(bin, { recursive: true }); + symlinkSync(process.execPath, join(bin, "bun")); + tool(tools, "grok", "chosen"); + tool(join(home, ".local", "bin"), "grok", "fallback"); + const result = await run(["/bin/sh", script, "-e", 'console.log(Bun.which("grok"))']); + expect(result.code).toBe(0); + expect(result.out.trim()).toBe(join(tools, "grok")); + }); + it("exits 127 when Bun is missing from PATH and the configured locations", async () => { const result = await run(["/bin/sh", script, "x"], { WITH_BUN_EXTRA_DIRS: join(home, "missing") }); expect(result.code).toBe(127);