Add web terminal, git credentials, OpenSCAD, review loops, automations
- Web terminal (PTY over WebSocket, xterm.js, sandboxed, reattach with replay) - Per-user git credentials and commit identity (credential store in the user's home) - OpenSCAD tool (STL/3MF + preview renders, bounding box) and cad skill; 3D model card - Craft phase inferred from tool activity when the agent does not report it - Configurable review loop per mode (rounds, reviewer model, criteria) - Delete config and Markdown agents from the agent editor - Automations: cron-scheduled agents from Markdown job files, schedule tool, page - change_directory tool (only on the user's request) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0d0e707f2
commit
df1924b0a1
41 files changed
+2809
-34
No files matched your search
+37
-15
@@ -82,36 +82,56 @@ pub fn command_with(spec: &SandboxSpec, cwd: &Path, extra_ro: &[&Path], program:
|
||||
let mut c = Command::new("bwrap");
|
||||
// Empty environment: bubblewrap's own helper process must not carry secrets.
|
||||
c.env_clear();
|
||||
c.args(bwrap_args(spec, cwd, extra_ro, true, program, args));
|
||||
c
|
||||
}
|
||||
|
||||
/// Arguments for `bwrap` running `program args` in the sandbox. `new_session`
|
||||
/// detaches from the controlling terminal (protects the caller's tty); a
|
||||
/// terminal with its own PTY leaves it off to keep job control.
|
||||
pub fn bwrap_args(spec: &SandboxSpec, cwd: &Path, extra_ro: &[&Path], new_session: bool, program: &str, args: &[&str]) -> Vec<std::ffi::OsString> {
|
||||
let mut c: Vec<std::ffi::OsString> = Vec::new();
|
||||
let mut push = |items: &[&dyn AsRef<std::ffi::OsStr>]| {
|
||||
for i in items {
|
||||
c.push(i.as_ref().to_os_string());
|
||||
}
|
||||
};
|
||||
for dir in ["/usr", "/bin", "/lib", "/etc", "/sbin", "/opt"] {
|
||||
if Path::new(dir).is_dir() && !Path::new(dir).is_symlink() {
|
||||
c.args(["--ro-bind", dir, dir]);
|
||||
push(&[&"--ro-bind", &dir, &dir]);
|
||||
} else if let Ok(target) = std::fs::read_link(dir) {
|
||||
c.arg("--symlink").arg(target).arg(dir);
|
||||
push(&[&"--symlink", &target, &dir]);
|
||||
}
|
||||
}
|
||||
for dir in ["/lib64", "/lib32"] {
|
||||
let p = Path::new(dir);
|
||||
if let Ok(target) = std::fs::read_link(p) {
|
||||
c.arg("--symlink").arg(target).arg(dir);
|
||||
push(&[&"--symlink", &target, &dir]);
|
||||
} else if p.is_dir() {
|
||||
c.args(["--ro-bind", dir, dir]);
|
||||
push(&[&"--ro-bind", &dir, &dir]);
|
||||
}
|
||||
}
|
||||
for root in &spec.roots {
|
||||
c.arg("--bind").arg(root).arg(root);
|
||||
push(&[&"--bind", root, root]);
|
||||
}
|
||||
// Built-in skills and templates are readable (not writable) in the sandbox.
|
||||
let builtin = crate::prompts::builtin_resources();
|
||||
for dir in extra_ro.iter().copied().chain(std::iter::once(builtin.as_path())) {
|
||||
if dir.exists() {
|
||||
c.arg("--ro-bind").arg(dir).arg(dir);
|
||||
push(&[&"--ro-bind", &dir, &dir]);
|
||||
}
|
||||
}
|
||||
let home = spec.home_inside();
|
||||
c.arg("--bind").arg(&spec.home).arg(&home);
|
||||
c.args(["--tmpfs", "/tmp", "--dev", "/dev", "--proc", "/proc"]);
|
||||
c.args(["--unshare-all", "--share-net", "--die-with-parent", "--new-session", "--clearenv"]);
|
||||
push(&[&"--bind", &spec.home, &home]);
|
||||
push(&[&"--tmpfs", &"/tmp", &"--dev", &"/dev", &"--proc", &"/proc"]);
|
||||
push(&[&"--unshare-all", &"--share-net", &"--die-with-parent", &"--clearenv"]);
|
||||
if new_session {
|
||||
push(&[&"--new-session"]);
|
||||
}
|
||||
let path = format!("{home}/.local/bin:{home}/.cargo/bin:{home}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin");
|
||||
let npm = format!("{home}/.npm-global");
|
||||
let pipx = format!("{home}/.local/pipx");
|
||||
let pipx_bin = format!("{home}/.local/bin");
|
||||
for (k, v) in [
|
||||
("HOME", home.as_str()),
|
||||
("USER", spec.username.as_str()),
|
||||
@@ -122,13 +142,15 @@ pub fn command_with(spec: &SandboxSpec, cwd: &Path, extra_ro: &[&Path], program:
|
||||
("GIT_TERMINAL_PROMPT", "0"),
|
||||
("PAGER", "cat"),
|
||||
("PIP_BREAK_SYSTEM_PACKAGES", "1"),
|
||||
("NPM_CONFIG_PREFIX", npm.as_str()),
|
||||
("PIPX_HOME", pipx.as_str()),
|
||||
("PIPX_BIN_DIR", pipx_bin.as_str()),
|
||||
] {
|
||||
c.args(["--setenv", k, v]);
|
||||
push(&[&"--setenv", &k, &v]);
|
||||
}
|
||||
push(&[&"--chdir", &cwd, &program]);
|
||||
for a in args {
|
||||
push(&[a]);
|
||||
}
|
||||
c.arg("--setenv").arg("NPM_CONFIG_PREFIX").arg(format!("{home}/.npm-global"));
|
||||
c.arg("--setenv").arg("PIPX_HOME").arg(format!("{home}/.local/pipx"));
|
||||
c.arg("--setenv").arg("PIPX_BIN_DIR").arg(format!("{home}/.local/bin"));
|
||||
c.arg("--chdir").arg(cwd);
|
||||
c.arg(program).args(args);
|
||||
c
|
||||
}
|
||||
Reference in new issue
Block a user