Generalize Pi sandbox launcher

This commit is contained in:
jb
2026-08-25 15:38:17 -04:00
parent 31c9363dbb
commit bab3c4589f
17 changed files with 637 additions and 391 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ The root `package.json` exposes the extensions and skills as a single Pi package
## Nono sandbox
See [`docs/nono.md`](docs/nono.md) for `pi-local` usage, profile settings, Internet tiers, multi-repository access, and the `nono-profiles` editing skill.
Use `pi-sbx` to launch Pi with a nono profile selected from the current directory. Manage routes with `pi-sbx-profile set <profile> [directory]`; omitting the directory assigns the current working directory. See [`docs/nono.md`](docs/nono.md) for setup, profile settings, Internet tiers, multi-repository access, and the `nono-profiles` editing skill.
## Install locally
+18
View File
@@ -0,0 +1,18 @@
{
"defaultProfile": "pi-orca-local",
"defaultRuntime": {
"nodeExecutable": "/usr/bin/node",
"piAgentDirectory": "~/.pi/agent",
"piExecutable": "~/.bun/bin/pi"
},
"directories": {
"/srv/data/code/harness-tools": "pi-harness-tools",
"/srv/work": "pi-work"
},
"profileRuntimes": {
"pi-work": {
"nodeExecutable": "/home/jb/.local/share/fnm/node-versions/v22.23.2/installation/bin/node",
"piAgentDirectory": "~/.pi/profiles/cyone"
}
}
}
+27 -18
View File
@@ -37,30 +37,39 @@ nono why --self --path /resolved/path --op readwrite
A profile change affects the next sandbox launch. It cannot expand the permissions of the current process.
## Orca Pi runner routing
## Directory-based Pi sandbox routing
Orca launches Pi through [`scripts/pi-orca.fish`](../scripts/pi-orca.fish). The runner resolves the canonical Git project through [`scripts/orca-nono-profile.py`](../scripts/orca-nono-profile.py), then selects the nono profile from [`profiles/orca/project-nono-profiles.json`](../profiles/orca/project-nono-profiles.json).
[`pi-sbx`](../scripts/pi-sbx.fish) launches Pi with a nono profile and runtime selected from the current directory. It works from Orca or any terminal. [`pi-sbx-profile`](../scripts/pi-sbx-profile.py) manages the trusted user configuration at `~/.config/pi-sbx/config.json`. Set `PI_SBX_CONFIG` to an absolute path to use a different configuration file. Both commands resolve `nono` from `PATH`.
The runner treats the runtime, sandbox, and Pi agent directory as one configuration:
| Route | Nono profile | Node runtime | Pi agent directory |
| --- | --- | --- | --- |
| Default | `pi-orca-local` | system Node 26 | `~/.pi/agent` |
| `/srv/work` descendants | `pi-work` | fnm Node 22.23.2 | `~/.pi/profiles/cyone` |
The separate Pi agent directories are required because native extension dependencies such as `better-sqlite3` are compiled for one Node ABI. Never point Node 22 and Node 26 runners at the same `PI_CODING_AGENT_DIR`.
Exact canonical-project mappings override root mappings. Root mappings use the longest matching ancestor of either the canonical Git root or the actual launch path. This keeps linked worktrees launched below `/srv/work` on the work stack even when their main checkout lives elsewhere, while `/srv/work-other` does not match `/srv/work`.
One-time setup links the commands and systemd slice, then copies the example configuration:
```bash
orca-nono-profile show /srv/work/code/example
orca-nono-profile set pi-work /srv/work/code/example
orca-nono-profile unset /srv/work/code/example
orca-nono-profile set-root pi-work /srv/work
orca-nono-profile unset-root /srv/work
mkdir -p ~/.local/bin ~/.config/pi-sbx ~/.config/systemd/user
ln -s /absolute/path/to/harness-tools/scripts/pi-sbx.fish ~/.local/bin/pi-sbx
ln -s /absolute/path/to/harness-tools/scripts/pi-sbx-profile.py ~/.local/bin/pi-sbx-profile
ln -s /absolute/path/to/harness-tools/systemd/user/pi-agents.slice ~/.config/systemd/user/pi-agents.slice
cp /absolute/path/to/harness-tools/config/pi-sbx.example.json ~/.config/pi-sbx/config.json
chmod 600 ~/.config/pi-sbx/config.json
systemctl --user daemon-reload
```
After changing a route, Node runtime, or Pi profile, restart the Orca terminal. Existing Pi processes cannot switch runtimes or agent directories. The runner fails closed when its trusted resolver or resolved nono profile is unavailable; it never falls back from a work route to the default Node 26 state.
Directory keys must be canonical absolute physical paths. Directory mappings apply to the configured directory and all descendants. The longest matching directory wins; otherwise `defaultProfile` is used. Resolution uses the physical launch path and does not inspect Git metadata. A linked worktree therefore inherits the route for the directory where the worktree is located.
```bash
pi-sbx-profile show # explain the route for CWD
pi-sbx-profile set pi-work # assign CWD and descendants
pi-sbx-profile set pi-work /srv/work # assign an explicit directory
pi-sbx-profile unset # remove the CWD assignment
pi-sbx-profile resolve /srv/work/example # print the selected profile
pi-sbx-profile list # print the configuration
pi-sbx # launch Pi using the selected route
```
The configuration keeps the sandbox, Node runtime, Pi executable, and Pi agent directory together. Start from [`config/pi-sbx.example.json`](../config/pi-sbx.example.json). `defaultRuntime` applies to every profile; an entry in `profileRuntimes` overrides only the fields that differ for that profile.
Separate Pi agent directories are required when runtimes use different Node ABIs. Native dependencies such as `better-sqlite3` must not be shared between the Node 22 and Node 26 agent directories.
The launcher exports `PI_SBX_PROFILE` and, for Orca compatibility, `ORCA_NONO_PROFILE`. Existing `pi-orca` and `orca-nono-profile` commands remain compatibility entry points. Restart an existing terminal after changing its route, runtime, or agent directory because a running Pi process cannot switch them.
## Edit profiles with the skill
-8
View File
@@ -1,8 +0,0 @@
{
"defaultProfile": "pi-orca-local",
"projectRoots": {
"/srv/work": "pi-work",
"/srv/data/code/harness-tools": "pi-harness-tools"
},
"projects": {}
}
+16 -177
View File
@@ -1,191 +1,30 @@
#!/usr/bin/env python3
"""Manage trusted project-to-nono-profile mappings for Orca-launched Pi."""
"""Compatibility entry point for pi-sbx-profile."""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import tempfile
import shutil
import sys
from pathlib import Path
from typing import Any
MAP_PATH = Path("/srv/data/code/harness-tools/profiles/orca/project-nono-profiles.json")
DEFAULT_PROFILE = "pi-orca-local"
def git_output(path: Path, *args: str) -> str | None:
result = subprocess.run(
["git", "-C", str(path), *args],
check=False,
capture_output=True,
text=True,
)
if result.returncode != 0:
return None
value = result.stdout.strip()
return value or None
ALIASES = {
"set-root": "set",
"unset-root": "unset",
}
def canonical_project_root(path: Path) -> Path:
candidate = path.expanduser().resolve()
common_dir = git_output(candidate, "rev-parse", "--path-format=absolute", "--git-common-dir")
if common_dir:
common_path = Path(common_dir).resolve()
if common_path.name == ".git":
return common_path.parent
def main() -> None:
executable = shutil.which("pi-sbx-profile")
if executable is None:
executable = str(Path(__file__).resolve().with_name("pi-sbx-profile.py"))
top_level = git_output(candidate, "rev-parse", "--show-toplevel")
if top_level:
return Path(top_level).resolve()
return candidate
def load_map() -> dict[str, Any]:
try:
contents = MAP_PATH.read_text(encoding="utf-8")
data = json.loads(contents)
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"cannot load profile map: {error}") from error
if not isinstance(data, dict):
raise ValueError("profile map must be a JSON object")
projects = data.get("projects")
if not isinstance(projects, dict):
raise ValueError("profile map projects must be a JSON object")
project_roots = data.get("projectRoots", {})
if not isinstance(project_roots, dict):
raise ValueError("profile map projectRoots must be a JSON object")
for root, profile in project_roots.items():
if not isinstance(root, str) or not Path(root).is_absolute():
raise ValueError("projectRoots keys must be absolute paths")
if not isinstance(profile, str) or not profile:
raise ValueError("projectRoots values must be non-empty profile names")
default = data.get("defaultProfile", DEFAULT_PROFILE)
if not isinstance(default, str) or not default:
raise ValueError("defaultProfile must be a non-empty string")
return data
def save_map(data: dict[str, Any]) -> None:
MAP_PATH.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary_name = tempfile.mkstemp(prefix=".project-nono-profiles.", dir=MAP_PATH.parent)
temporary = Path(temporary_name)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
json.dump(data, handle, indent=2, sort_keys=True)
handle.write("\n")
temporary.chmod(0o600)
os.replace(temporary, MAP_PATH)
finally:
temporary.unlink(missing_ok=True)
def profile_exists(profile: str) -> bool:
result = subprocess.run(
["nono", "profile", "show", profile],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return result.returncode == 0
def resolve_profile(
data: dict[str, Any], project: Path, launch_path: Path | None = None
) -> tuple[str, str]:
projects = data["projects"]
mapped = projects.get(str(project))
if isinstance(mapped, str) and mapped:
return mapped, "project"
route_candidates = {project}
if launch_path is not None:
route_candidates.add(launch_path)
matching_roots: list[tuple[Path, str]] = []
for root_value, profile in data.get("projectRoots", {}).items():
root = Path(root_value)
if any(candidate == root or root in candidate.parents for candidate in route_candidates):
matching_roots.append((root, profile))
if matching_roots:
_root, profile = max(matching_roots, key=lambda match: len(match[0].parts))
return profile, "project-root"
return data.get("defaultProfile", DEFAULT_PROFILE), "default"
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
for command in ("resolve", "show", "unset", "unset-root"):
subparser = subparsers.add_parser(command)
subparser.add_argument("path", nargs="?", default=os.getcwd())
for command in ("set", "set-root"):
set_parser = subparsers.add_parser(command)
set_parser.add_argument("profile")
set_parser.add_argument("path", nargs="?", default=os.getcwd())
subparsers.add_parser("list")
args = parser.parse_args()
try:
data = load_map()
except ValueError as error:
parser.error(str(error))
if args.command == "list":
print(json.dumps(data, indent=2, sort_keys=True))
return 0
launch_path = Path(args.path).expanduser().resolve()
project = canonical_project_root(launch_path)
if args.command == "resolve":
profile, _source = resolve_profile(data, project, launch_path)
print(profile)
return 0
if args.command == "show":
profile, source = resolve_profile(data, project, launch_path)
print(
json.dumps(
{
"launchPath": str(launch_path),
"projectRoot": str(project),
"profile": profile,
"source": source,
},
indent=2,
sort_keys=True,
)
)
return 0
if args.command in ("set", "set-root"):
if not profile_exists(args.profile):
parser.error(f"nono profile does not exist: {args.profile}")
mapping_key = "projects" if args.command == "set" else "projectRoots"
mapping_path = project if args.command == "set" else launch_path
data.setdefault(mapping_key, {})[str(mapping_path)] = args.profile
save_map(data)
print(f"{mapping_path} -> {args.profile}")
return 0
if args.command in ("unset", "unset-root"):
mapping_key = "projects" if args.command == "unset" else "projectRoots"
mapping_path = project if args.command == "unset" else launch_path
removed = data.setdefault(mapping_key, {}).pop(str(mapping_path), None)
save_map(data)
if removed is None:
print(f"no mapping for {mapping_path}")
else:
print(f"removed {mapping_path} -> {removed}")
return 0
raise AssertionError(f"unhandled command: {args.command}")
arguments = sys.argv[1:]
if arguments:
arguments[0] = ALIASES.get(arguments[0], arguments[0])
os.execv(executable, [executable, *arguments])
if __name__ == "__main__":
raise SystemExit(main())
main()
-14
View File
@@ -1,14 +0,0 @@
function configure_pi_orca_runtime --argument-names profile
set -g pi_orca_node_executable /usr/bin/node
set -gx PI_CODING_AGENT_DIR /home/jb/.pi/agent
if test "$profile" = pi-work
set pi_orca_node_executable /home/jb/.local/share/fnm/node-versions/v22.23.2/installation/bin/node
set PI_CODING_AGENT_DIR /home/jb/.pi/profiles/cyone
end
# Keep Pi and its subprocesses on the same Node installation. Orca inherits
# fnm's PATH, which otherwise makes tool commands resolve a different Node.
set -l node_bin (path dirname "$pi_orca_node_executable")
set -gx PATH "$node_bin" $PATH
end
+9 -47
View File
@@ -1,53 +1,15 @@
#!/usr/bin/env fish
# Select a nono profile from a trusted canonical-project map, then launch Pi in
# its own bounded cgroup. Unknown or invalid projects fail closed.
set -l resolver /home/jb/.local/bin/orca-nono-profile
# Compatibility entry point for existing Orca configuration.
set -l launcher (command -s pi-sbx)
if test -z "$launcher"
set -l script_directory (path dirname (status filename))
set launcher "$script_directory/pi-sbx.fish"
end
if not test -x "$resolver"
printf 'pi-orca: profile resolver is unavailable: %s\n' "$resolver" >&2
if not test -x "$launcher"
printf 'pi-orca: pi-sbx is unavailable: %s\n' "$launcher" >&2
exit 1
end
set -l profile ("$resolver" resolve "$PWD" 2>/dev/null)
if test $status -ne 0; or test -z "$profile"
printf 'pi-orca: could not resolve a trusted profile for %s\n' "$PWD" >&2
exit 1
end
if not /usr/bin/nono profile show "$profile" >/dev/null 2>&1
printf 'pi-orca: resolved profile is unavailable: %s\n' "$profile" >&2
exit 1
end
source /srv/data/code/harness-tools/scripts/pi-orca-runtime.fish
configure_pi_orca_runtime "$profile"
set -l node_executable "$pi_orca_node_executable"
set -l pi_agent_dir "$PI_CODING_AGENT_DIR"
if not test -x "$node_executable"
printf 'pi-orca: Node runtime is unavailable: %s\n' "$node_executable" >&2
exit 1
end
if not test -d "$pi_agent_dir"
printf 'pi-orca: Pi agent directory is unavailable: %s\n' "$pi_agent_dir" >&2
exit 1
end
set -lx ORCA_NONO_PROFILE "$profile"
set -lx PI_CODING_AGENT_DIR "$pi_agent_dir"
exec /usr/bin/systemd-run \
--user \
--scope \
--collect \
--quiet \
--slice=orca-agents.slice \
--property=CPUQuota=1600% \
--property=MemoryHigh=48G \
--property=MemoryMax=64G \
--property=MemorySwapMax=8G \
--property=TasksMax=8192 \
--property=OOMPolicy=stop \
-- /usr/bin/nono run --profile "$profile" -- "$node_executable" /home/jb/.bun/bin/pi $argv
exec "$launcher" $argv
+236
View File
@@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""Manage directory-to-profile mappings for pi-sbx."""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import tempfile
from pathlib import Path
from typing import Any
DEFAULT_CONFIG_PATH = Path("~/.config/pi-sbx/config.json")
RUNTIME_FIELDS = ("nodeExecutable", "piExecutable", "piAgentDirectory")
def config_path() -> Path:
override = os.environ.get("PI_SBX_CONFIG")
if override:
selected_path = Path(override).expanduser()
if not selected_path.is_absolute():
raise ValueError("PI_SBX_CONFIG must be an absolute path or start with ~")
return selected_path
xdg_config_home = os.environ.get("XDG_CONFIG_HOME")
if xdg_config_home:
selected_directory = Path(xdg_config_home).expanduser()
if selected_directory.is_absolute():
return selected_directory / "pi-sbx" / "config.json"
return DEFAULT_CONFIG_PATH.expanduser()
def validate_runtime(runtime: Any, field_name: str, *, require_all: bool) -> None:
if not isinstance(runtime, dict):
raise ValueError(f"{field_name} must be a JSON object")
unknown_fields = set(runtime) - set(RUNTIME_FIELDS)
if unknown_fields:
unknown = ", ".join(sorted(unknown_fields))
raise ValueError(f"{field_name} contains unknown fields: {unknown}")
if require_all:
missing_fields = set(RUNTIME_FIELDS) - set(runtime)
if missing_fields:
missing = ", ".join(sorted(missing_fields))
raise ValueError(f"{field_name} is missing fields: {missing}")
for key, value in runtime.items():
if not isinstance(value, str) or not value:
raise ValueError(f"{field_name}.{key} must be a non-empty path")
if not Path(value).expanduser().is_absolute():
raise ValueError(f"{field_name}.{key} must be an absolute path or start with ~")
def validate_config(data: Any) -> dict[str, Any]:
if not isinstance(data, dict):
raise ValueError("configuration must be a JSON object")
default_profile = data.get("defaultProfile")
if not isinstance(default_profile, str) or not default_profile:
raise ValueError("defaultProfile must be a non-empty profile name")
directories = data.get("directories")
if not isinstance(directories, dict):
raise ValueError("directories must be a JSON object")
for directory, profile in directories.items():
if not isinstance(directory, str):
raise ValueError("directories keys must be canonical absolute paths")
configured_path = Path(directory).expanduser()
if not configured_path.is_absolute() or str(configured_path.resolve()) != directory:
raise ValueError("directories keys must be canonical absolute physical paths")
if not isinstance(profile, str) or not profile:
raise ValueError("directories values must be non-empty profile names")
validate_runtime(data.get("defaultRuntime"), "defaultRuntime", require_all=True)
profile_runtimes = data.get("profileRuntimes", {})
if not isinstance(profile_runtimes, dict):
raise ValueError("profileRuntimes must be a JSON object")
for profile, runtime in profile_runtimes.items():
if not isinstance(profile, str) or not profile:
raise ValueError("profileRuntimes keys must be non-empty profile names")
validate_runtime(runtime, f"profileRuntimes.{profile}", require_all=False)
return data
def load_config(path: Path | None = None) -> dict[str, Any]:
selected_path = path or config_path()
try:
contents = selected_path.read_text(encoding="utf-8")
data = json.loads(contents)
except (OSError, json.JSONDecodeError) as error:
raise ValueError(f"cannot load configuration from {selected_path}: {error}") from error
return validate_config(data)
def save_config(data: dict[str, Any], path: Path | None = None) -> None:
validate_config(data)
selected_path = path or config_path()
selected_path.parent.mkdir(parents=True, exist_ok=True)
descriptor, temporary_name = tempfile.mkstemp(prefix=".config.", dir=selected_path.parent)
temporary = Path(temporary_name)
try:
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
json.dump(data, handle, indent=2, sort_keys=True)
handle.write("\n")
temporary.chmod(0o600)
os.replace(temporary, selected_path)
finally:
temporary.unlink(missing_ok=True)
def resolve_profile(data: dict[str, Any], path: Path) -> tuple[str, str, Path | None]:
launch_path = path.expanduser().resolve()
matches: list[tuple[Path, str]] = []
for directory_value, profile in data["directories"].items():
directory = Path(directory_value).expanduser().resolve()
if launch_path == directory or directory in launch_path.parents:
matches.append((directory, profile))
if matches:
directory, profile = max(matches, key=lambda match: len(match[0].parts))
return profile, "directory", directory
return data["defaultProfile"], "default", None
def runtime_for_profile(data: dict[str, Any], profile: str) -> dict[str, str]:
runtime = dict(data["defaultRuntime"])
runtime.update(data.get("profileRuntimes", {}).get(profile, {}))
return {key: str(Path(runtime[key]).expanduser()) for key in RUNTIME_FIELDS}
def resolved_configuration(data: dict[str, Any], path: Path) -> dict[str, Any]:
launch_path = path.expanduser().resolve()
profile, source, directory = resolve_profile(data, launch_path)
return {
"launchPath": str(launch_path),
"matchedDirectory": str(directory) if directory else None,
"profile": profile,
"runtime": runtime_for_profile(data, profile),
"source": source,
}
def profile_exists(profile: str) -> bool:
result = subprocess.run(
["nono", "profile", "show", profile],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return result.returncode == 0
def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
resolve_parser = subparsers.add_parser("resolve")
resolve_parser.add_argument("path", nargs="?", default=os.getcwd())
resolve_parser.add_argument(
"--format",
choices=("profile", "launcher", "json"),
default="profile",
)
show_parser = subparsers.add_parser("show")
show_parser.add_argument("path", nargs="?", default=os.getcwd())
set_parser = subparsers.add_parser("set")
set_parser.add_argument("profile")
set_parser.add_argument("path", nargs="?", default=os.getcwd())
unset_parser = subparsers.add_parser("unset")
unset_parser.add_argument("path", nargs="?", default=os.getcwd())
subparsers.add_parser("list")
return parser
def main() -> int:
parser = create_parser()
args = parser.parse_args()
try:
data = load_config()
except ValueError as error:
parser.error(str(error))
if args.command == "list":
print(json.dumps(data, indent=2, sort_keys=True))
return 0
path = Path(args.path).expanduser().resolve()
if args.command in ("resolve", "show"):
resolved = resolved_configuration(data, path)
if args.command == "show" or args.format == "json":
print(json.dumps(resolved, indent=2, sort_keys=True))
elif args.format == "launcher":
runtime = resolved["runtime"]
print(resolved["profile"])
print(runtime["nodeExecutable"])
print(runtime["piExecutable"])
print(runtime["piAgentDirectory"])
else:
print(resolved["profile"])
return 0
if args.command == "set":
if not profile_exists(args.profile):
parser.error(f"nono profile does not exist: {args.profile}")
data["directories"][str(path)] = args.profile
save_config(data)
print(f"{path} -> {args.profile}")
return 0
if args.command == "unset":
removed = data["directories"].pop(str(path), None)
save_config(data)
if removed is None:
print(f"no mapping for {path}")
else:
print(f"removed {path} -> {removed}")
return 0
raise AssertionError(f"unhandled command: {args.command}")
if __name__ == "__main__":
raise SystemExit(main())
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env fish
# Select a trusted nono profile and matching Pi runtime from the current
# directory, then launch Pi in a bounded systemd scope.
set -l resolver
if set -q PI_SBX_PROFILE_COMMAND
set resolver "$PI_SBX_PROFILE_COMMAND"
else
set resolver (command -s pi-sbx-profile)
end
if test -z "$resolver"; or not test -x "$resolver"
printf 'pi-sbx: profile resolver is unavailable: %s\n' "$resolver" >&2
exit 1
end
set -l resolved ("$resolver" resolve "$PWD" --format launcher 2>/dev/null)
if test $status -ne 0; or test (count $resolved) -ne 4
printf 'pi-sbx: could not resolve a valid configuration for %s\n' "$PWD" >&2
exit 1
end
set -l profile "$resolved[1]"
set -l node_executable "$resolved[2]"
set -l pi_executable "$resolved[3]"
set -l pi_agent_dir "$resolved[4]"
set -l nono_executable (command -s nono)
if test -z "$nono_executable"; or not test -x "$nono_executable"
printf 'pi-sbx: nono is unavailable on PATH\n' >&2
exit 1
end
if not "$nono_executable" profile show "$profile" >/dev/null 2>&1
printf 'pi-sbx: resolved nono profile is unavailable: %s\n' "$profile" >&2
exit 1
end
if not test -x "$node_executable"
printf 'pi-sbx: Node runtime is unavailable: %s\n' "$node_executable" >&2
exit 1
end
if not test -f "$pi_executable"
printf 'pi-sbx: Pi executable is unavailable: %s\n' "$pi_executable" >&2
exit 1
end
if not test -d "$pi_agent_dir"
printf 'pi-sbx: Pi agent directory is unavailable: %s\n' "$pi_agent_dir" >&2
exit 1
end
set -l node_bin (path dirname "$node_executable")
set -gx PATH "$node_bin" $PATH
set -lx PI_SBX_PROFILE "$profile"
set -lx PI_CODING_AGENT_DIR "$pi_agent_dir"
# Keep Orca hook integrations working while callers migrate to PI_SBX_PROFILE.
set -lx ORCA_NONO_PROFILE "$profile"
exec /usr/bin/systemd-run \
--user \
--scope \
--collect \
--quiet \
--slice=pi-agents.slice \
--property=CPUQuota=1600% \
--property=MemoryHigh=48G \
--property=MemoryMax=64G \
--property=MemorySwapMax=8G \
--property=TasksMax=8192 \
--property=OOMPolicy=stop \
-- "$nono_executable" run --profile "$profile" -- "$node_executable" "$pi_executable" $argv
-65
View File
@@ -1,65 +0,0 @@
from __future__ import annotations
import importlib.util
import unittest
from pathlib import Path
MODULE_PATH = Path(__file__).with_name("orca-nono-profile.py")
SPEC = importlib.util.spec_from_file_location("orca_nono_profile", MODULE_PATH)
if SPEC is None or SPEC.loader is None:
raise RuntimeError(f"Could not load runner resolver from {MODULE_PATH}")
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
class ResolveProfileTests(unittest.TestCase):
def setUp(self) -> None:
self.mapping = {
"defaultProfile": "pi-orca-local",
"projectRoots": {
"/srv/work": "pi-work",
"/srv/work/special": "pi-special",
},
"projects": {
"/srv/work/special/override": "pi-override",
},
}
def test_exact_project_mapping_wins(self) -> None:
self.assertEqual(
MODULE.resolve_profile(self.mapping, Path("/srv/work/special/override")),
("pi-override", "project"),
)
def test_longest_project_root_wins(self) -> None:
self.assertEqual(
MODULE.resolve_profile(self.mapping, Path("/srv/work/special/repository")),
("pi-special", "project-root"),
)
def test_project_root_matches_descendants(self) -> None:
self.assertEqual(
MODULE.resolve_profile(self.mapping, Path("/srv/work/code/repository")),
("pi-work", "project-root"),
)
def test_linked_worktree_uses_its_launch_path_root(self) -> None:
self.assertEqual(
MODULE.resolve_profile(
self.mapping,
Path("/home/jb/code/main-checkout"),
Path("/srv/work/worktrees/feature"),
),
("pi-work", "project-root"),
)
def test_project_root_does_not_match_similar_prefix(self) -> None:
self.assertEqual(
MODULE.resolve_profile(self.mapping, Path("/srv/worker/repository")),
("pi-orca-local", "default"),
)
if __name__ == "__main__":
unittest.main()
-60
View File
@@ -1,60 +0,0 @@
from __future__ import annotations
import os
import subprocess
import unittest
from pathlib import Path
RUNTIME_SCRIPT = Path(__file__).with_name("pi-orca-runtime.fish")
SYSTEM_NODE = Path("/usr/bin/node")
WORK_NODE = Path(
"/home/jb/.local/share/fnm/node-versions/v22.23.2/installation/bin/node"
)
class ConfigurePiOrcaRuntimeTests(unittest.TestCase):
def configure_runtime(self, profile: str) -> list[str]:
environment = os.environ.copy()
environment["PATH"] = ":".join(
[str(WORK_NODE.parent), "/usr/local/bin", "/usr/bin"]
)
command = """
source $argv[1]
configure_pi_orca_runtime $argv[2]
printf '%s\\n' $pi_orca_node_executable $PI_CODING_AGENT_DIR $PATH[1] (command -v node)
"""
result = subprocess.run(
["/usr/bin/fish", "-c", command, str(RUNTIME_SCRIPT), profile],
check=True,
capture_output=True,
env=environment,
text=True,
)
return result.stdout.splitlines()
def test_default_profile_prioritizes_system_node(self) -> None:
self.assertEqual(
self.configure_runtime("pi-harness-tools"),
[
str(SYSTEM_NODE),
"/home/jb/.pi/agent",
str(SYSTEM_NODE.parent),
str(SYSTEM_NODE),
],
)
def test_work_profile_prioritizes_node_22(self) -> None:
self.assertEqual(
self.configure_runtime("pi-work"),
[
str(WORK_NODE),
"/home/jb/.pi/profiles/cyone",
str(WORK_NODE.parent),
str(WORK_NODE),
],
)
if __name__ == "__main__":
unittest.main()
+72
View File
@@ -0,0 +1,72 @@
from __future__ import annotations
import os
import subprocess
import tempfile
import unittest
from pathlib import Path
LAUNCHER = Path(__file__).with_name("pi-sbx.fish")
class PiSbxLauncherTests(unittest.TestCase):
def run_launcher_with_resolver(self, output: str) -> subprocess.CompletedProcess[str]:
with tempfile.TemporaryDirectory() as directory:
resolver = Path(directory) / "resolver"
resolver.write_text(f"#!/bin/sh\nprintf '%s' '{output}'\n", encoding="utf-8")
resolver.chmod(0o755)
environment = os.environ.copy()
environment["PI_SBX_PROFILE_COMMAND"] = str(resolver)
return subprocess.run(
["/usr/bin/fish", str(LAUNCHER)],
env=environment,
check=False,
capture_output=True,
text=True,
)
def test_rejects_incomplete_launcher_configuration(self) -> None:
result = self.run_launcher_with_resolver("pi-local\n/usr/bin/node\n")
self.assertEqual(result.returncode, 1)
self.assertIn("could not resolve a valid configuration", result.stderr)
def test_rejects_unavailable_profile(self) -> None:
result = self.run_launcher_with_resolver(
"profile-that-does-not-exist\n/usr/bin/node\n/home/jb/.bun/bin/pi\n/home/jb/.pi/agent\n"
)
self.assertEqual(result.returncode, 1)
self.assertIn("resolved nono profile is unavailable", result.stderr)
def test_uses_nono_from_path(self) -> None:
with tempfile.TemporaryDirectory() as directory:
temporary = Path(directory)
resolver = temporary / "resolver"
resolver.write_text(
"#!/bin/sh\nprintf '%b' 'pi-local\\n/missing/node\\n/missing/pi\\n/missing/agent\\n'\n",
encoding="utf-8",
)
resolver.chmod(0o755)
nono = temporary / "nono"
nono.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
nono.chmod(0o755)
environment = os.environ.copy()
environment["PI_SBX_PROFILE_COMMAND"] = str(resolver)
environment["PATH"] = f"{temporary}:{environment['PATH']}"
result = subprocess.run(
["/usr/bin/fish", str(LAUNCHER)],
env=environment,
check=False,
capture_output=True,
text=True,
)
self.assertEqual(result.returncode, 1)
self.assertIn("Node runtime is unavailable", result.stderr)
if __name__ == "__main__":
unittest.main()
+188
View File
@@ -0,0 +1,188 @@
from __future__ import annotations
import importlib.util
import json
import os
import subprocess
import tempfile
import unittest
from pathlib import Path
MODULE_PATH = Path(__file__).with_name("pi-sbx-profile.py")
SPEC = importlib.util.spec_from_file_location("pi_sbx_profile", MODULE_PATH)
if SPEC is None or SPEC.loader is None:
raise RuntimeError(f"Could not load profile resolver from {MODULE_PATH}")
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
class PiSbxProfileTests(unittest.TestCase):
def setUp(self) -> None:
self.config = {
"defaultProfile": "pi-local",
"directories": {
"/srv/work": "pi-work",
"/srv/work/special": "pi-special",
},
"defaultRuntime": {
"nodeExecutable": "/usr/bin/node",
"piExecutable": "~/.bun/bin/pi",
"piAgentDirectory": "~/.pi/agent",
},
"profileRuntimes": {
"pi-work": {
"nodeExecutable": "/opt/node-22/bin/node",
"piAgentDirectory": "~/.pi/profiles/work",
}
},
}
def test_longest_directory_match_wins(self) -> None:
self.assertEqual(
MODULE.resolve_profile(self.config, Path("/srv/work/special/repository")),
("pi-special", "directory", Path("/srv/work/special")),
)
def test_directory_matches_descendants(self) -> None:
self.assertEqual(
MODULE.resolve_profile(self.config, Path("/srv/work/code/repository")),
("pi-work", "directory", Path("/srv/work")),
)
def test_directory_does_not_match_similar_prefix(self) -> None:
self.assertEqual(
MODULE.resolve_profile(self.config, Path("/srv/worker/repository")),
("pi-local", "default", None),
)
def test_profile_runtime_overrides_defaults(self) -> None:
self.assertEqual(
MODULE.runtime_for_profile(self.config, "pi-work"),
{
"nodeExecutable": "/opt/node-22/bin/node",
"piExecutable": str(Path("~/.bun/bin/pi").expanduser()),
"piAgentDirectory": str(Path("~/.pi/profiles/work").expanduser()),
},
)
def test_default_runtime_is_used_without_override(self) -> None:
runtime = MODULE.runtime_for_profile(self.config, "pi-special")
self.assertEqual(runtime["nodeExecutable"], "/usr/bin/node")
self.assertEqual(runtime["piAgentDirectory"], str(Path("~/.pi/agent").expanduser()))
def test_configuration_requires_complete_default_runtime(self) -> None:
del self.config["defaultRuntime"]["piExecutable"]
with self.assertRaisesRegex(ValueError, "missing fields: piExecutable"):
MODULE.validate_config(self.config)
def test_configuration_rejects_noncanonical_directory(self) -> None:
self.config["directories"] = {"~/work": "pi-local"}
with self.assertRaisesRegex(ValueError, "canonical absolute physical paths"):
MODULE.validate_config(self.config)
def test_relative_xdg_config_home_is_ignored(self) -> None:
original_xdg = os.environ.get("XDG_CONFIG_HOME")
original_override = os.environ.pop("PI_SBX_CONFIG", None)
os.environ["XDG_CONFIG_HOME"] = "repository-controlled"
try:
self.assertEqual(MODULE.config_path(), MODULE.DEFAULT_CONFIG_PATH.expanduser())
finally:
if original_xdg is None:
os.environ.pop("XDG_CONFIG_HOME", None)
else:
os.environ["XDG_CONFIG_HOME"] = original_xdg
if original_override is not None:
os.environ["PI_SBX_CONFIG"] = original_override
def test_relative_config_override_is_rejected(self) -> None:
original_override = os.environ.get("PI_SBX_CONFIG")
os.environ["PI_SBX_CONFIG"] = "config.json"
try:
with self.assertRaisesRegex(ValueError, "PI_SBX_CONFIG must be an absolute path"):
MODULE.config_path()
finally:
if original_override is None:
os.environ.pop("PI_SBX_CONFIG", None)
else:
os.environ["PI_SBX_CONFIG"] = original_override
def test_save_and_load_configuration(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "pi-sbx" / "config.json"
MODULE.save_config(self.config, path)
self.assertEqual(MODULE.load_config(path), self.config)
self.assertEqual(path.stat().st_mode & 0o777, 0o600)
self.assertEqual(json.loads(path.read_text(encoding="utf-8")), self.config)
def test_set_without_path_assigns_current_directory(self) -> None:
with tempfile.TemporaryDirectory() as directory:
temporary = Path(directory)
config_path = temporary / "config.json"
workdir = temporary / "repository"
binary_directory = temporary / "bin"
workdir.mkdir()
binary_directory.mkdir()
MODULE.save_config(self.config, config_path)
nono = binary_directory / "nono"
nono.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
nono.chmod(0o755)
environment = os.environ.copy()
environment["PI_SBX_CONFIG"] = str(config_path)
environment["PATH"] = f"{binary_directory}:{environment['PATH']}"
result = subprocess.run(
[str(MODULE_PATH), "set", "pi-special"],
cwd=workdir,
env=environment,
check=True,
capture_output=True,
text=True,
)
self.assertEqual(result.stdout.strip(), f"{workdir} -> pi-special")
self.assertEqual(MODULE.load_config(config_path)["directories"][str(workdir)], "pi-special")
subprocess.run(
[str(MODULE_PATH), "unset"],
cwd=workdir,
env=environment,
check=True,
capture_output=True,
text=True,
)
self.assertNotIn(str(workdir), MODULE.load_config(config_path)["directories"])
def test_legacy_set_root_command_assigns_current_directory(self) -> None:
with tempfile.TemporaryDirectory() as directory:
temporary = Path(directory)
config_path = temporary / "config.json"
workdir = temporary / "repository"
binary_directory = temporary / "bin"
workdir.mkdir()
binary_directory.mkdir()
MODULE.save_config(self.config, config_path)
nono = binary_directory / "nono"
nono.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
nono.chmod(0o755)
environment = os.environ.copy()
environment["PI_SBX_CONFIG"] = str(config_path)
environment["PATH"] = f"{binary_directory}:{environment['PATH']}"
subprocess.run(
[str(Path(__file__).with_name("orca-nono-profile.py")), "set-root", "pi-special"],
cwd=workdir,
env=environment,
check=True,
capture_output=True,
text=True,
)
self.assertEqual(MODULE.load_config(config_path)["directories"][str(workdir)], "pi-special")
if __name__ == "__main__":
unittest.main()
@@ -1,5 +1,5 @@
[Unit]
Description=Resource envelope for Orca-managed AI agent sessions
Description=Resource envelope for Pi agent sessions
[Slice]
CPUQuota=3200%