diff --git a/.Rbuildignore b/.Rbuildignore index aadd852..cf170d6 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -8,6 +8,6 @@ ^\.devcontainer$ ^\.github$ ^\.claude$ -^replr/tests/testthat/_snaps.*$ +^tests/testthat/_snaps.*$ ^doc$ ^Meta$ diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 21f0e82..469d654 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,6 @@ # Use the official Rocker R image as base -FROM rocker/r-ver:4.4 +# Image pinned by SHA256 digest for supply chain security (rocker/r-ver:4.4 as of 2025-11-25) +FROM rocker/r-ver@sha256:80bf33ee1b61f3252129912867abf23339ef1d9e96c837cbd86341a23c3fef07 # Install system dependencies required for R packages RUN apt-get update -y && apt-get install -y \ @@ -27,7 +28,7 @@ RUN apt-get update -y && apt-get install -y \ # Set CRAN repository to improve package installation reliability RUN echo 'options(repos = c(CRAN = "https://cran.rstudio.com/"))' >> /usr/local/lib/R/etc/Rprofile.site -# Install required R packages for replr in order of dependency complexity +# Install required R packages for repljail in order of dependency complexity RUN Rscript -e "install.packages('R6', repos='https://cran.rstudio.com/', dependencies=TRUE)" RUN Rscript -e "install.packages('uuid', repos='https://cran.rstudio.com/', dependencies=TRUE)" RUN Rscript -e "install.packages('processx', repos='https://cran.rstudio.com/', dependencies=TRUE)" @@ -41,7 +42,7 @@ RUN Rscript -e "install.packages(c('pryr', 'testthat'), repos='https://cran.rstu RUN Rscript -e "install.packages(c('devtools', 'roxygen2', 'pkgdown', 'lintr', 'remotes', 'here', 'languageserver', 'httpgd', 'png', 'pak', 'ellmer', 'covr', 'knitr', 'rmarkdown', 'DT', 'htmltools'), repos='https://cran.rstudio.com/', dependencies=TRUE)" # Set working directory -WORKDIR /workspaces/replr +WORKDIR /workspaces/repljail # Add health check HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ diff --git a/.devcontainer/README.md b/.devcontainer/README.md deleted file mode 100644 index 720aeff..0000000 --- a/.devcontainer/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Custom GitHub Copilot Environment for replr - -This directory contains configuration files to set up a custom development environment for GitHub Copilot agents working with the `replr` R package. - -## Overview - -The custom environment ensures that: -- R (version 4.3.2) is pre-installed -- All required R packages are available -- Development tools are ready to use -- GitHub Copilot extensions are enabled - -## Files - -### `devcontainer.json` -Main configuration file that defines: -- Base Docker image built from the Dockerfile -- VS Code extensions (R support, GitHub Copilot) -- Post-creation commands to verify setup -- User configuration - -### `Dockerfile` -Builds a custom container with: -- R 4.3.2 base image (rocker/r-ver) -- System dependencies for R package compilation -- Pre-installed required packages: - - `nanonext` - NNG (nanomsg) messaging library - - `processx` - Process spawning and management - - `evaluate` - Safe evaluation of R expressions - - `R6` - Encapsulated object-oriented programming - - `uuid` - UUID generation -- Pre-installed suggested packages: - - `pryr` - Memory usage monitoring - - `testthat` - Unit testing framework -- Development tools: - - `devtools` - Package development tools - - `roxygen2` - Documentation generation - - `pkgdown` - Website generation - - `lintr` - Code linting - -## Usage - -When you open this repository in GitHub Codespaces or a VS Code dev container, the environment will automatically: - -1. Build the custom Docker image with all dependencies -2. Install VS Code extensions for R development and GitHub Copilot -3. Verify that all required packages are correctly installed -4. Set up the workspace for immediate development - -## Benefits for GitHub Copilot - -This pre-configured environment allows GitHub Copilot to: -- Understand package dependencies and suggest appropriate code -- Work with R-specific syntax and idioms effectively -- Access documentation and examples for installed packages -- Provide contextually relevant suggestions for the replr package - -## Verification - -After the container starts, the environment will automatically run a comprehensive test via `test-environment.R`. You can also manually verify the setup: - -```bash -# Run the full environment test -Rscript .devcontainer/test-environment.R - -# Check R version -R --version - -# Verify required packages -Rscript -e "required <- c('nanonext', 'processx', 'evaluate', 'R6', 'uuid'); sapply(required, function(x) cat(x, ':', as.character(packageVersion(x)), '\n'))" - -# Load the replr package in development mode -Rscript -e "devtools::load_all(); replr::check_dependencies()" -``` - -## Troubleshooting - -If packages are missing or there are installation issues: - -1. Rebuild the container: Command Palette → "Dev Containers: Rebuild Container" -2. Check the build logs for specific error messages -3. Verify system dependencies are correctly installed -4. Ensure CRAN mirrors are accessible - -## Maintenance - -To update package versions: -1. Modify the `Dockerfile` to specify new versions -2. Update the verification commands in `devcontainer.json` -3. Test the build locally before committing changes diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a43b2c4..fec82d4 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ { - "name": "R Environment for replr", + "name": "R Environment for repljail", "build": { "dockerfile": "Dockerfile", "context": ".." diff --git a/.devcontainer/test-environment.R b/.devcontainer/test-environment.R index cb48eea..e8a692f 100755 --- a/.devcontainer/test-environment.R +++ b/.devcontainer/test-environment.R @@ -3,7 +3,7 @@ # Test script to validate the custom Copilot environment setup # This script should be run inside the devcontainer to verify everything works -cat("=== Testing Custom GitHub Copilot Environment for replr ===\n\n") +cat("=== Testing Custom GitHub Copilot Environment for repljail ===\n\n") # Test 1: Check R version cat("1. R Version:\n") @@ -43,19 +43,19 @@ for (pkg in dev_packages) { } # Test 5: Test package dependency check function -cat("\n5. Testing replr dependency check:\n") +cat("\n5. Testing repljail dependency check:\n") tryCatch( { if (file.exists("R/utils.R")) { source("R/utils.R") result <- check_dependencies() - cat(" ✓ replr::check_dependencies() passed\n") + cat(" ✓ repljail::check_dependencies() passed\n") } else { cat(" ⚠ R/utils.R not found (run from package root)\n") } }, error = function(e) { - cat(sprintf(" ✗ replr::check_dependencies() failed: %s\n", e$message)) + cat(sprintf(" ✗ repljail::check_dependencies() failed: %s\n", e$message)) } ) diff --git a/CLAUDE.md b/CLAUDE.md index 2401b64..67463ec 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -# replr - Isolated R REPL +# repljail - Isolated R REPL Process-based isolated R REPL implementation that provides secure, sandboxed R code execution using separate worker processes. Designed for running untrusted or potentially problematic R code without affecting the parent process. @@ -42,10 +42,10 @@ cleanup_docker_containers() cleanup_docker_networks() # Enable Docker mode for workers -options(replr.worker.type = "docker") +options(repljail.worker.type = "docker") # Enable network isolation (requires Docker mode) -options(replr.worker.docker.network.isolation = TRUE) +options(repljail.worker.docker.network.isolation = TRUE) ``` **Firejail Support (Linux)** @@ -54,10 +54,10 @@ options(replr.worker.docker.network.isolation = TRUE) is_firejail_available() # Enable Firejail mode for workers -options(replr.worker.type = "firejail") +options(repljail.worker.type = "firejail") # Use custom Firejail profile (optional) -options(replr.worker.firejail.profile = "/path/to/profile.profile") +options(repljail.worker.firejail.profile = "/path/to/profile.profile") ``` **macOS Sandbox Support (macOS only)** @@ -66,13 +66,13 @@ options(replr.worker.firejail.profile = "/path/to/profile.profile") is_macos_sandbox_available() # Enable macOS sandbox mode for workers -options(replr.worker.type = "macos-sandbox") +options(repljail.worker.type = "macos-sandbox") # Use custom sandbox profile (optional) -options(replr.worker.macos.sandbox.profile = "/path/to/profile.sb") +options(repljail.worker.macos.sandbox.profile = "/path/to/profile.sb") ``` -**Worker Type Selection**: Set `replr.worker.type` to one of: `"native"`, `"docker"`, `"firejail"`, or `"macos-sandbox"` (default: `"native"`) +**Worker Type Selection**: Set `repljail.worker.type` to one of: `"native"`, `"docker"`, `"firejail"`, or `"macos-sandbox"` (default: `"native"`) ### Debug Logging ```r @@ -123,7 +123,7 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process - **inst/worker.R**: Worker script that runs in isolated process, handles code evaluation ### Worker Process Details -- Located at `inst/worker.R` (or system.file("worker.R", package = "replr") when installed) +- Located at `inst/worker.R` (or system.file("worker.R", package = "repljail") when installed) - Started with: `Rscript worker.R [--debug] [--listen-all]` - Uses `evaluate::evaluate()` with `stop_on_error = 2` (continues after errors) - Plots captured via `recordedplot` objects, converted to base64 PNG data URLs @@ -131,19 +131,20 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process ### Docker Integration - Dockerfile in `inst/Dockerfile` based on `rocker/r-ver:4.4` +- **Supply chain security**: All base images pinned by SHA256 digest (rocker/r-ver, alpine/socat) - Security features: non-root user, read-only filesystem, capability dropping, memory/CPU limits -- Container naming: `replr-worker--` for cleanup tracking -- Configurable via options: `replr.worker.docker.image`, `replr.worker.docker.memory`, `replr.worker.docker.cpus` -- **Network Isolation (Sidecar Pattern)**: Optional air-gapped execution with `replr.worker.docker.network.isolation` +- Container naming: `repljail-worker--` for cleanup tracking +- Configurable via options: `repljail.worker.docker.image`, `repljail.worker.docker.memory`, `repljail.worker.docker.cpus` +- **Network Isolation (Sidecar Pattern)**: Optional air-gapped execution with `repljail.worker.docker.network.isolation` - Creates `--internal` bridge network (blocks all outbound traffic including internet) - Worker container: Connected to isolated network only (zero internet access) - - Gateway sidecar: `alpine/socat` container that bridges host ↔ worker communication + - Gateway sidecar: `alpine/socat` container (pinned by SHA256) that bridges host ↔ worker communication - Architecture: ``` Host (127.0.0.1:) ← Docker publish → Gateway Container ← Internal Network → Worker Container (air-gapped) ``` - - Network naming: `replr-network--` - - Gateway naming: `replr-gateway--` + - Network naming: `repljail-network--` + - Gateway naming: `repljail-gateway--` - Automatic cleanup of worker, gateway, and network when session stops - Security: Worker has ZERO external network access while host communication works via gateway proxy @@ -151,7 +152,7 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process - Lightweight sandboxing for Linux systems using firejail - Implemented in `R/worker-wrappers.R` via `FirejailWorkerWrapper` R6 class - Availability detection: `is_firejail_available()` checks for firejail command -- Configurable via options: `replr.worker.type = "firejail"`, `replr.worker.firejail.profile` +- Configurable via options: `repljail.worker.type = "firejail"`, `repljail.worker.firejail.profile` - **Default Security Settings**: - Network isolation: `--net=lo` (loopback only, blocks external access) - Filesystem isolation: `--private-tmp` (isolated temp directory) @@ -159,7 +160,7 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process - Seccomp filtering: `--seccomp` (restrict system calls) - No privilege escalation: `--nonewprivs`, `--noroot` - Hardware restrictions: `--nosound`, `--novideo`, `--no3d`, `--nodvd`, `--notv` -- **Custom Profiles**: Support for custom firejail profile files via `replr.worker.firejail.profile` option +- **Custom Profiles**: Support for custom firejail profile files via `repljail.worker.firejail.profile` option - **Process Management**: Worker executed as `firejail [options] Rscript worker.R ` - **Tests**: `tests/testthat/test-firejail.R` (skipped on CI and non-Linux systems) - **Demo**: `inst/examples/firejail-demo.R` shows complete usage @@ -168,7 +169,7 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process - Native sandboxing for macOS using Apple's `sandbox-exec` command - Implemented in `R/worker-wrappers.R` via `MacOSSandboxWorkerWrapper` R6 class - Availability detection: `is_macos_sandbox_available()` checks for macOS and sandbox-exec command -- Configurable via options: `replr.worker.type = "macos-sandbox"`, `replr.worker.macos.sandbox.profile` +- Configurable via options: `repljail.worker.type = "macos-sandbox"`, `repljail.worker.macos.sandbox.profile` - **Default Security Profile** (auto-generated using Sandbox Profile Language): - **Filesystem access**: - Read: All files (allows R to read system files, libraries, data) @@ -188,7 +189,7 @@ Main R Process (Controller) ←──nanonext REQ/REP──→ Worker R Process ### ellmer Integration - Functions in `R/ellmer-tools.R` provide LLM agent tools for the ellmer package -- Global session registry (`.replr_sessions`) tracks active sessions across tool calls +- Global session registry (`.repljail_sessions`) tracks active sessions across tool calls - Auto-generated session IDs: `--` (e.g., "red-eagle-742") - All tools return standardized responses: `list(success, message, data, error)` - Plot handling: Converts base64 plots to temporary PNG files for LLM consumption @@ -297,21 +298,20 @@ jj describe -m "Descriptive commit message" Global options control package behavior: **General:** -- `replr.debug` (logical): Enable debug logging +- `repljail.debug` (logical): Enable debug logging **Worker Type Selection:** -- `replr.worker.type` (string): Worker isolation method - one of "native", "docker", "firejail", "macos-sandbox" (default: "native") - - Legacy boolean options (`replr.use.docker`, `replr.use.firejail`, `replr.use.macos.sandbox`) are deprecated but supported with warnings +- `repljail.worker.type` (string): Worker isolation method - one of "native", "docker", "firejail", "macos-sandbox" (default: "native") **Docker Configuration:** -- `replr.worker.docker.image` (string): Docker image name (default: "replr-worker:latest") -- `replr.worker.docker.memory` (string): Memory limit (default: "512m") -- `replr.worker.docker.cpus` (string): CPU limit (default: "1.0") -- `replr.worker.docker.network.isolation` (logical): Enable isolated Docker networks (default: FALSE) +- `repljail.worker.docker.image` (string): Docker image name (default: "repljail-worker:latest") +- `repljail.worker.docker.memory` (string): Memory limit (default: "512m") +- `repljail.worker.docker.cpus` (string): CPU limit (default: "1.0") +- `repljail.worker.docker.network.isolation` (logical): Enable isolated Docker networks (default: FALSE) **Firejail Configuration:** -- `replr.worker.firejail.profile` (string): Path to custom firejail profile file (default: NULL) +- `repljail.worker.firejail.profile` (string): Path to custom firejail profile file (default: NULL) **macOS Sandbox Configuration:** -- `replr.worker.macos.sandbox.profile` (string): Path to custom sandbox profile (.sb) file (default: NULL) +- `repljail.worker.macos.sandbox.profile` (string): Path to custom sandbox profile (.sb) file (default: NULL) - to memorize : when testing scripts and examples, always use devtools::load_all() and source from within an R session instead of using Rscript \ No newline at end of file diff --git a/DESCRIPTION b/DESCRIPTION index afb7632..daeb3d2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,11 +1,11 @@ -Package: replr +Package: repljail Type: Package Title: Isolated REPL functionality for R Version: 0.1.0 Author: Peter Krusche Maintainer: Peter Krusche Description: Provides isolated REPL functionality for R, allowing users to execute R code in a separate environment. -License: MIT + file LICENSE +License: GPL (>= 3) Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 diff --git a/LICENSE.md b/LICENSE.md index 9edd548..175443c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,21 +1,595 @@ -# MIT License - -Copyright (c) 2025 Peter Krusche - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +GNU General Public License +========================== + +_Version 3, 29 June 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +## Preamble + +The GNU General Public License is a free, copyleft license for software and other +kinds of works. + +The licenses for most software and other practical works are designed to take away +your freedom to share and change the works. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change all versions of a +program--to make sure it remains free software for all its users. We, the Free +Software Foundation, use the GNU General Public License for most of our software; it +applies also to any other work released this way by its authors. You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General +Public Licenses are designed to make sure that you have the freedom to distribute +copies of free software (and charge for them if you wish), that you receive source +code or can get it if you want it, that you can change the software or use pieces of +it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or +asking you to surrender the rights. Therefore, you have certain responsibilities if +you distribute copies of the software, or if you modify it: responsibilities to +respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, +you must pass on to the recipients the same freedoms that you received. You must make +sure that they, too, receive or can get the source code. And you must show them these +terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: **(1)** assert +copyright on the software, and **(2)** offer you this License giving you legal permission +to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is +no warranty for this free software. For both users' and authors' sake, the GPL +requires that modified versions be marked as changed, so that their problems will not +be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of +the software inside them, although the manufacturer can do so. This is fundamentally +incompatible with the aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we have designed +this version of the GPL to prohibit the practice for those products. If such problems +arise substantially in other domains, we stand ready to extend this provision to +those domains in future versions of the GPL, as needed to protect the freedom of +users. + +Finally, every program is threatened constantly by software patents. States should +not allow patents to restrict development and use of software on general-purpose +computers, but in those that do, we wish to avoid the special danger that patents +applied to a free program could make it effectively proprietary. To prevent this, the +GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in +a fashion requiring copyright permission, other than the making of an exact copy. The +resulting work is called a “modified version” of the earlier work or a +work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on +the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for infringement under +applicable copyright law, except executing it on a computer or modifying a private +copy. Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the +extent that it includes a convenient and prominently visible feature that **(1)** +displays an appropriate copyright notice, and **(2)** tells the user that there is no +warranty for the work (except to the extent that warranties are provided), that +licensees may convey the work under this License, and how to view a copy of this +License. If the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code + +The “source code” for a work means the preferred form of the work for +making modifications to it. “Object code” means any non-source form of a +work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of interfaces +specified for a particular programming language, one that is widely used among +developers working in that language. + +The “System Libraries” of an executable work include anything, other than +the work as a whole, that **(a)** is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and **(b)** serves only to +enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code form. +A “Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on which +the executable work runs, or a compiler used to produce the work, or an object code +interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the +source code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. However, +it does not include the work's System Libraries, or general-purpose tools or +generally available free programs which are used unmodified in performing those +activities but which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for the work, and +the source code for shared libraries and dynamically linked subprograms that the work +is specifically designed to require, such as by intimate data communication or +control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +### 2. Basic Permissions + +All rights granted under this License are granted for the term of copyright on the +Program, and are irrevocable provided the stated conditions are met. This License +explicitly affirms your unlimited permission to run the unmodified Program. The +output from running a covered work is covered by this License only if the output, +given its content, constitutes a covered work. This License acknowledges your rights +of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey covered +works to others for the sole purpose of having them make modifications exclusively +for you, or provide you with facilities for running those works, provided that you +comply with the terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for you must do so +exclusively on your behalf, under your direction and control, on terms that prohibit +them from making any copies of your copyrighted material outside their relationship +with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law + +No covered work shall be deemed part of an effective technological measure under any +applicable law fulfilling obligations under article 11 of the WIPO copyright treaty +adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention +of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of +technological measures to the extent such circumvention is effected by exercising +rights under this License with respect to the covered work, and you disclaim any +intention to limit operation or modification of the work as a means of enforcing, +against the work's users, your or third parties' legal rights to forbid circumvention +of technological measures. + +### 4. Conveying Verbatim Copies + +You may convey verbatim copies of the Program's source code as you receive it, in any +medium, provided that you conspicuously and appropriately publish on each copy an +appropriate copyright notice; keep intact all notices stating that this License and +any non-permissive terms added in accord with section 7 apply to the code; keep +intact all notices of the absence of any warranty; and give all recipients a copy of +this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer +support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions + +You may convey a work based on the Program, or the modifications to produce it from +the Program, in the form of source code under the terms of section 4, provided that +you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified it, and giving a +relevant date. +* **b)** The work must carry prominent notices stating that it is released under this +License and any conditions added under section 7. This requirement modifies the +requirement in section 4 to “keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this License to anyone who +comes into possession of a copy. This License will therefore apply, along with any +applicable section 7 additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no permission to license the +work in any other way, but it does not invalidate such permission if you have +separately received it. +* **d)** If the work has interactive user interfaces, each must display Appropriate Legal +Notices; however, if the Program has interactive interfaces that do not display +Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are +not by their nature extensions of the covered work, and which are not combined with +it such as to form a larger program, in or on a volume of a storage or distribution +medium, is called an “aggregate” if the compilation and its resulting +copyright are not used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work in an aggregate +does not cause this License to apply to the other parts of the aggregate. + +### 6. Conveying Non-Source Forms + +You may convey a covered work in object code form under the terms of sections 4 and +5, provided that you also convey the machine-readable Corresponding Source under the +terms of this License, in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by the Corresponding Source fixed on a +durable physical medium customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by a written offer, valid for at least +three years and valid for as long as you offer spare parts or customer support for +that product model, to give anyone who possesses the object code either **(1)** a copy of +the Corresponding Source for all the software in the product that is covered by this +License, on a durable physical medium customarily used for software interchange, for +a price no more than your reasonable cost of physically performing this conveying of +source, or **(2)** access to copy the Corresponding Source from a network server at no +charge. +* **c)** Convey individual copies of the object code with a copy of the written offer to +provide the Corresponding Source. This alternative is allowed only occasionally and +noncommercially, and only if you received the object code with such an offer, in +accord with subsection 6b. +* **d)** Convey the object code by offering access from a designated place (gratis or for +a charge), and offer equivalent access to the Corresponding Source in the same way +through the same place at no further charge. You need not require recipients to copy +the Corresponding Source along with the object code. If the place to copy the object +code is a network server, the Corresponding Source may be on a different server +(operated by you or a third party) that supports equivalent copying facilities, +provided you maintain clear directions next to the object code saying where to find +the Corresponding Source. Regardless of what server hosts the Corresponding Source, +you remain obligated to ensure that it is available for as long as needed to satisfy +these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are being +offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A “User Product” is either **(1)** a “consumer product”, which +means any tangible personal property which is normally used for personal, family, or +household purposes, or **(2)** anything designed or sold for incorporation into a +dwelling. In determining whether a product is a consumer product, doubtful cases +shall be resolved in favor of coverage. For a particular product received by a +particular user, “normally used” refers to a typical or common use of +that class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, the +product. A product is a consumer product regardless of whether the product has +substantial commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified version of +its Corresponding Source. The information must suffice to ensure that the continued +functioning of the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for +use in, a User Product, and the conveying occurs as part of a transaction in which +the right of possession and use of the User Product is transferred to the recipient +in perpetuity or for a fixed term (regardless of how the transaction is +characterized), the Corresponding Source conveyed under this section must be +accompanied by the Installation Information. But this requirement does not apply if +neither you nor any third party retains the ability to install modified object code +on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to +continue to provide support service, warranty, or updates for a work that has been +modified or installed by the recipient, or for the User Product in which it has been +modified or installed. Access to a network may be denied when the modification itself +materially and adversely affects the operation of the network or violates the rules +and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with +this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require no +special password or key for unpacking, reading or copying. + +### 7. Additional Terms + +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. Additional +permissions that are applicable to the entire Program shall be treated as though they +were included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may be +used separately under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when you +modify the work.) You may place additional permissions on material, added by you to a +covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed by works +containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that +modified versions of such material be marked in reasonable ways as different from the +original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or authors of the +material; or +* **e)** Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that material by anyone +who conveys the material (or modified versions of it) with contractual assumptions of +liability to the recipient, for any liability that these contractual assumptions +directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you received +it, or any part of it, contains a notice stating that it is governed by this License +along with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or conveying +under this License, you may add to a covered work material governed by the terms of +that license document, provided that the further restriction does not survive such +relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in +the relevant source files, a statement of the additional terms that apply to those +files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements apply +either way. + +### 8. Termination + +You may not propagate or modify a covered work except as expressly provided under +this License. Any attempt otherwise to propagate or modify it is void, and will +automatically terminate your rights under this License (including any patent licenses +granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated **(a)** provisionally, unless and until the +copyright holder explicitly and finally terminates your license, and **(b)** permanently, +if the copyright holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, this +is the first time you have received notice of violation of this License (for any +work) from that copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of +parties who have received copies or rights from you under this License. If your +rights have been terminated and not permanently reinstated, you do not qualify to +receive new licenses for the same material under section 10. + +### 9. Acceptance Not Required for Having Copies + +You are not required to accept this License in order to receive or run a copy of the +Program. Ancillary propagation of a covered work occurring solely as a consequence of +using peer-to-peer transmission to receive a copy likewise does not require +acceptance. However, nothing other than this License grants you permission to +propagate or modify any covered work. These actions infringe copyright if you do not +accept this License. Therefore, by modifying or propagating a covered work, you +indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients + +Each time you convey a covered work, the recipient automatically receives a license +from the original licensors, to run, modify and propagate that work, subject to this +License. You are not responsible for enforcing compliance by third parties with this +License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an organization, or +merging organizations. If propagation of a covered work results from an entity +transaction, each party to that transaction who receives a copy of the work also +receives whatever licenses to the work the party's predecessor in interest had or +could give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if the predecessor +has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or +affirmed under this License. For example, you may not impose a license fee, royalty, +or other charge for exercise of rights granted under this License, and you may not +initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging +that any patent claim is infringed by making, using, selling, offering for sale, or +importing the Program or any portion of it. + +### 11. Patents + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The work thus +licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, that +would be infringed by some manner, permitted by this License, of making, using, or +selling its contributor version, but do not include claims that would be infringed +only as a consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant patent +sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license +under the contributor's essential patent claims, to make, use, sell, offer for sale, +import and otherwise run, modify and propagate the contents of its contributor +version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent (such as an +express permission to practice a patent or covenant not to sue for patent +infringement). To “grant” such a patent license to a party means to make +such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of charge +and under the terms of this License, through a publicly available network server or +other readily accessible means, then you must either **(1)** cause the Corresponding +Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner consistent with +the requirements of this License, to extend the patent license to downstream +recipients. “Knowingly relying” means you have actual knowledge that, but +for the patent license, your conveying the covered work in a country, or your +recipient's use of the covered work in a country, would infringe one or more +identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a patent +license to some of the parties receiving the covered work authorizing them to use, +propagate, modify or convey a specific copy of the covered work, then the patent +license you grant is automatically extended to all recipients of the covered work and +works based on it. + +A patent license is “discriminatory” if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on the +non-exercise of one or more of the rights that are specifically granted under this +License. You may not convey a covered work if you are a party to an arrangement with +a third party that is in the business of distributing software, under which you make +payment to the third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties who would receive +the covered work from you, a discriminatory patent license **(a)** in connection with +copies of the covered work conveyed by you (or copies made from those copies), or **(b)** +primarily for and in connection with specific products or compilations that contain +the covered work, unless you entered into that arrangement, or that patent license +was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to you +under applicable patent law. + +### 12. No Surrender of Others' Freedom + +If conditions are imposed on you (whether by court order, agreement or otherwise) +that contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot convey a covered work so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not convey it at all. For example, if you +agree to terms that obligate you to collect a royalty for further conveying from +those to whom you convey the Program, the only way you could satisfy both those terms +and this License would be to refrain entirely from conveying the Program. + +### 13. Use with the GNU Affero General Public License + +Notwithstanding any other provision of this License, you have permission to link or +combine any covered work with a work licensed under version 3 of the GNU Affero +General Public License into a single combined work, and to convey the resulting work. +The terms of this License will continue to apply to the part which is the covered +work, but the special requirements of the GNU Affero General Public License, section +13, concerning interaction through a network will apply to the combination as such. + +### 14. Revised Versions of this License + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in spirit +to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that +a certain numbered version of the GNU General Public License “or any later +version” applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by the +Free Software Foundation. If the Program does not specify a version number of the GNU +General Public License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU +General Public License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no +additional obligations are imposed on any author or copyright holder as a result of +your choosing to follow a later version. + +### 15. Disclaimer of Warranty + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE +OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16 + +If the disclaimer of warranty and limitation of liability provided above cannot be +given local legal effect according to their terms, reviewing courts shall apply local +law that most closely approximates an absolute waiver of all civil liability in +connection with the Program, unless a warranty or assumption of liability accompanies +a copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to +the public, the best way to achieve this is to make it free software which everyone +can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them +to the start of each source file to most effectively state the exclusion of warranty; +and each file should have at least the “copyright” line and a pointer to +where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this +when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type 'show c' for details. + +The hypothetical commands `show w` and `show c` should show the appropriate parts of +the General Public License. Of course, your program's commands might be different; +for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to +sign a “copyright disclaimer” for the program, if necessary. For more +information on this, and how to apply and follow the GNU GPL, see +<>. + +The GNU General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may consider it +more useful to permit linking proprietary applications with the library. If this is +what you want to do, use the GNU Lesser General Public License instead of this +License. But first, please read +<>. diff --git a/NAMESPACE b/NAMESPACE index d3c961e..ba99295 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,26 +11,26 @@ export(enable_debug) export(is_docker_available) export(is_firejail_available) export(is_macos_sandbox_available) -export(replr_check_syntax) -export(replr_check_syntax_tool) -export(replr_cleanup_sessions) -export(replr_cleanup_sessions_tool) -export(replr_create_repl_session) -export(replr_create_repl_session_tool) -export(replr_execute_code) -export(replr_execute_code_tool) -export(replr_get_session_info) -export(replr_get_session_info_tool) -export(replr_lint_code) -export(replr_lint_code_tool) -export(replr_list_sessions) -export(replr_list_sessions_tool) -export(replr_run_r_code) -export(replr_run_r_code_tool) -export(replr_stop_all_sessions) -export(replr_stop_all_sessions_tool) -export(replr_stop_session) -export(replr_stop_session_tool) +export(repljail_check_syntax) +export(repljail_check_syntax_tool) +export(repljail_cleanup_sessions) +export(repljail_cleanup_sessions_tool) +export(repljail_create_repl_session) +export(repljail_create_repl_session_tool) +export(repljail_execute_code) +export(repljail_execute_code_tool) +export(repljail_get_session_info) +export(repljail_get_session_info_tool) +export(repljail_lint_code) +export(repljail_lint_code_tool) +export(repljail_list_sessions) +export(repljail_list_sessions_tool) +export(repljail_run_r_code) +export(repljail_run_r_code_tool) +export(repljail_stop_all_sessions) +export(repljail_stop_all_sessions_tool) +export(repljail_stop_session) +export(repljail_stop_session_tool) importFrom(R6,R6Class) importFrom(cli,cli_alert_danger) importFrom(cli,cli_alert_info) diff --git a/R/conversation-logger.R b/R/conversation-logger.R index 8ffe739..5a6809f 100644 --- a/R/conversation-logger.R +++ b/R/conversation-logger.R @@ -8,7 +8,7 @@ #' Create a logger and attach it to an ellmer Chat object: #' \preformatted{ #' library(ellmer) -#' library(replr) +#' library(repljail) #' #' # Create a chat #' chat <- chat_openai() @@ -225,7 +225,7 @@ ConversationLogger <- R6::R6Class( } # Special handling for R code execution - if (tool_name == "replr_execute_code") { + if (tool_name == "repljail_execute_code") { if (!is.null(args$code)) { private$append_log("**Code:**\n\n") private$append_log("```r\n") @@ -235,7 +235,7 @@ ConversationLogger <- R6::R6Class( if (!is.null(args$session_id)) { private$append_log(paste0("**Session:** ", args$session_id, "\n\n")) } - } else if (tool_name == "replr_run_r_code") { + } else if (tool_name == "repljail_run_r_code") { if (!is.null(args$code)) { private$append_log("**Code:**\n\n") private$append_log("```r\n") @@ -275,7 +275,7 @@ ConversationLogger <- R6::R6Class( } } - # Check if this is a replr tool result (standard format) + # Check if this is a repljail tool result (standard format) success_val <- get_prop(result, "success") if (!is.null(success_val)) { private$append_log(paste0( diff --git a/R/debug-example.R b/R/debug-example.R index cde61a4..92cc46d 100644 --- a/R/debug-example.R +++ b/R/debug-example.R @@ -1,17 +1,17 @@ #' Enable Debug Logging #' -#' Convenience function to enable debug logging for the replr package +#' Convenience function to enable debug logging for the repljail package #' #' @param enable logical, TRUE to enable debug logging, FALSE to disable #' @export enable_debug <- function(enable = TRUE) { - options(replr.debug = enable) + options(repljail.debug = enable) if (enable) { - cli::cli_alert_success("Debug logging enabled for replr package") - cli::cli_alert_info("Use options(replr.debug = FALSE) to disable") + cli::cli_alert_success("Debug logging enabled for repljail package") + cli::cli_alert_info("Use options(repljail.debug = FALSE) to disable") } else { - cli::cli_alert_info("Debug logging disabled for replr package") + cli::cli_alert_info("Debug logging disabled for repljail package") } invisible(enable) @@ -23,7 +23,7 @@ enable_debug <- function(enable = TRUE) { #' #' @export debug_status <- function() { - status <- getOption("replr.debug", default = FALSE) + status <- getOption("repljail.debug", default = FALSE) if (status) { cli::cli_alert_success("Debug logging is currently ENABLED") diff --git a/R/debug.R b/R/debug.R index 961cbbb..542c10d 100644 --- a/R/debug.R +++ b/R/debug.R @@ -1,12 +1,12 @@ #' Debug Logging Utilities #' -#' Configurable debug logging using the cli package and the replr.debug option +#' Configurable debug logging using the cli package and the repljail.debug option #' Check if debug logging is enabled #' #' @return logical, TRUE if debug logging is enabled is_debug_enabled <- function() { - getOption("replr.debug", default = FALSE) + getOption("repljail.debug", default = FALSE) } #' Debug log message diff --git a/R/ellmer-tools.R b/R/ellmer-tools.R index a9162d5..77687e2 100644 --- a/R/ellmer-tools.R +++ b/R/ellmer-tools.R @@ -1,4 +1,4 @@ -#' replr Tools for REPL Session Management +#' repljail Tools for REPL Session Management #' #' This module provides tools designed specifically for LLM agents (e.g. in ellmer) to #' create, manage, and use isolated R REPL sessions. These functions provide @@ -6,12 +6,12 @@ #' optimized for LLM consumption. #' #' @section Session Management: -#' The replr tools maintain a global registry of active REPL sessions, +#' The repljail tools maintain a global registry of active REPL sessions, #' allowing LLM agents to create multiple concurrent sessions and manage #' them independently. #' #' @section Response Format: -#' All replr tools return standardized responses with the following structure: +#' All repljail tools return standardized responses with the following structure: #' \itemize{ #' \item \code{success} - logical, whether the operation succeeded #' \item \code{message} - character, human-readable status message @@ -19,10 +19,10 @@ #' \item \code{error} - character, error message (when applicable) #' } -# Global session registry for replr tools -.replr_sessions <- new.env(parent = emptyenv()) +# Global session registry for repljail tools +.repljail_sessions <- new.env(parent = emptyenv()) -#' Create a New REPL Session for replr +#' Create a New REPL Session for repljail #' #' Creates a new isolated R REPL session that can be used by an LLM agent. #' Each session runs in a separate R process and maintains its own environment. @@ -34,13 +34,13 @@ #' @examples #' \dontrun{ #' # Create a new session with auto-generated ID -#' result <- replr_create_repl_session() +#' result <- repljail_create_repl_session() #' if (result$success) { #' session_id <- result$data$session_id #' cat("Created session:", session_id) #' } #' } -replr_create_repl_session <- function(timeout = 10) { +repljail_create_repl_session <- function(timeout = 10) { tryCatch( { # Auto-generate session ID @@ -48,7 +48,7 @@ replr_create_repl_session <- function(timeout = 10) { counter <- 1 while ( is.null(session_id) || - (exists(session_id, envir = .replr_sessions) && counter < 10) + (exists(session_id, envir = .repljail_sessions) && counter < 10) ) { session_id <- paste0( sample( @@ -85,10 +85,10 @@ replr_create_repl_session <- function(timeout = 10) { } # Create new REPL session with Docker if available - session <- replr::RREPLSession$new(timeout = timeout) + session <- repljail::RREPLSession$new(timeout = timeout) # Store in registry - assign(session_id, session, envir = .replr_sessions) + assign(session_id, session, envir = .repljail_sessions) # Get session information session_info <- session$get_info() @@ -124,7 +124,7 @@ replr_create_repl_session <- function(timeout = 10) { #' Execute R Code in a REPL Session #' -#' Executes R code in a specified replr REPL session and returns the results +#' Executes R code in a specified repljail REPL session and returns the results #' in a structured format suitable for LLM processing. #' #' @param session_id character, ID of the session to execute code in @@ -135,26 +135,26 @@ replr_create_repl_session <- function(timeout = 10) { #' @examples #' \dontrun{ #' # Create a session and execute code -#' session_result <- replr_create_repl_session() +#' session_result <- repljail_create_repl_session() #' session_id <- session_result$data$session_id #' #' # Execute simple arithmetic -#' result <- replr_execute_code(session_id, "2 + 2") +#' result <- repljail_execute_code(session_id, "2 + 2") #' if (result$success) { #' cat("Output:", result$data$output) #' } #' #' # Execute more complex code -#' result <- replr_execute_code(session_id, " +#' result <- repljail_execute_code(session_id, " #' data <- data.frame(x = 1:5, y = letters[1:5]) #' summary(data) #' ") #' } -replr_execute_code <- function(session_id, code, timeout = 30) { +repljail_execute_code <- function(session_id, code, timeout = 30) { tryCatch( { # Check if session exists - if (!exists(session_id, envir = .replr_sessions)) { + if (!exists(session_id, envir = .repljail_sessions)) { return(list( success = FALSE, message = paste("Session not found:", session_id), @@ -164,7 +164,7 @@ replr_execute_code <- function(session_id, code, timeout = 30) { } # Get session - session <- get(session_id, envir = .replr_sessions) + session <- get(session_id, envir = .repljail_sessions) # Check if session is still alive if (!session$is_alive()) { @@ -248,7 +248,7 @@ replr_execute_code <- function(session_id, code, timeout = 30) { #' Get Information About a REPL Session #' -#' Retrieves detailed information about a specific replr REPL session, +#' Retrieves detailed information about a specific repljail REPL session, #' including its status, process information, and activity. #' #' @param session_id character, ID of the session to query @@ -257,16 +257,16 @@ replr_execute_code <- function(session_id, code, timeout = 30) { #' @examples #' \dontrun{ #' # Get information about a session -#' result <- replr_get_session_info("my_session_id") +#' result <- repljail_get_session_info("my_session_id") #' if (result$success) { #' print(result$data) #' } #' } -replr_get_session_info <- function(session_id) { +repljail_get_session_info <- function(session_id) { tryCatch( { # Check if session exists - if (!exists(session_id, envir = .replr_sessions)) { + if (!exists(session_id, envir = .repljail_sessions)) { return(list( success = FALSE, message = paste("Session not found:", session_id), @@ -276,7 +276,7 @@ replr_get_session_info <- function(session_id) { } # Get session - session <- get(session_id, envir = .replr_sessions) + session <- get(session_id, envir = .repljail_sessions) # Get session information session_info <- session$get_info() @@ -313,7 +313,7 @@ replr_get_session_info <- function(session_id) { #' Stop a REPL Session #' -#' Gracefully stops a specified replr REPL session and removes it from +#' Gracefully stops a specified repljail REPL session and removes it from #' the session registry. #' #' @param session_id character, ID of the session to stop @@ -323,16 +323,16 @@ replr_get_session_info <- function(session_id) { #' @examples #' \dontrun{ #' # Stop a specific session -#' result <- replr_stop_session("my_session_id") +#' result <- repljail_stop_session("my_session_id") #' if (result$success) { #' cat("Session stopped successfully") #' } #' } -replr_stop_session <- function(session_id, timeout = 5) { +repljail_stop_session <- function(session_id, timeout = 5) { tryCatch( { # Check if session exists - if (!exists(session_id, envir = .replr_sessions)) { + if (!exists(session_id, envir = .repljail_sessions)) { return(list( success = FALSE, message = paste("Session not found:", session_id), @@ -342,13 +342,13 @@ replr_stop_session <- function(session_id, timeout = 5) { } # Get session - session <- get(session_id, envir = .replr_sessions) + session <- get(session_id, envir = .repljail_sessions) # Stop the session stop_result <- session$stop(timeout = timeout) # Remove from registry - rm(list = session_id, envir = .replr_sessions) + rm(list = session_id, envir = .repljail_sessions) list( success = TRUE, @@ -376,7 +376,7 @@ replr_stop_session <- function(session_id, timeout = 5) { #' List All Active REPL Sessions #' -#' Returns a list of all currently active replr REPL sessions with their +#' Returns a list of all currently active repljail REPL sessions with their #' basic information. #' #' @return list with information about all active sessions @@ -384,17 +384,17 @@ replr_stop_session <- function(session_id, timeout = 5) { #' @examples #' \dontrun{ #' # List all active sessions -#' result <- replr_list_sessions() +#' result <- repljail_list_sessions() #' if (result$success) { #' for (session in result$data$sessions) { #' cat("Session:", session$session_id, "- Alive:", session$is_alive, "\n") #' } #' } #' } -replr_list_sessions <- function() { +repljail_list_sessions <- function() { tryCatch( { - session_ids <- ls(envir = .replr_sessions) + session_ids <- ls(envir = .repljail_sessions) if (length(session_ids) == 0) { return(list( @@ -411,7 +411,7 @@ replr_list_sessions <- function() { # Collect information about all sessions sessions_info <- list() for (session_id in session_ids) { - session <- get(session_id, envir = .replr_sessions) + session <- get(session_id, envir = .repljail_sessions) session_info <- session$get_info() sessions_info[[length(sessions_info) + 1]] <- list( @@ -447,7 +447,7 @@ replr_list_sessions <- function() { #' Clean Up Dead REPL Sessions #' -#' Removes dead sessions from the replr session registry. This is useful +#' Removes dead sessions from the repljail session registry. This is useful #' for cleanup when sessions may have died unexpectedly. #' #' @return list with cleanup results @@ -455,25 +455,25 @@ replr_list_sessions <- function() { #' @examples #' \dontrun{ #' # Clean up any dead sessions -#' result <- replr_cleanup_sessions() +#' result <- repljail_cleanup_sessions() #' cat("Cleaned up", result$data$cleaned_count, "dead sessions") #' } -replr_cleanup_sessions <- function() { +repljail_cleanup_sessions <- function() { tryCatch( { - session_ids <- ls(envir = .replr_sessions) + session_ids <- ls(envir = .repljail_sessions) cleaned_count <- 0 dead_sessions <- character(0) for (session_id in session_ids) { - session <- get(session_id, envir = .replr_sessions) + session <- get(session_id, envir = .repljail_sessions) if (!session$is_alive()) { # Try to stop it gracefully first tryCatch(session$stop(timeout = 1), error = function(e) {}) # Remove from registry - rm(list = session_id, envir = .replr_sessions) + rm(list = session_id, envir = .repljail_sessions) cleaned_count <- cleaned_count + 1 dead_sessions <- c(dead_sessions, session_id) } @@ -485,7 +485,7 @@ replr_cleanup_sessions <- function() { data = list( cleaned_count = cleaned_count, dead_sessions = dead_sessions, - remaining_sessions = length(ls(envir = .replr_sessions)) + remaining_sessions = length(ls(envir = .repljail_sessions)) ), error = NULL ) @@ -503,7 +503,7 @@ replr_cleanup_sessions <- function() { #' Stop All REPL Sessions #' -#' Stops all active replr REPL sessions and clears the session registry. +#' Stops all active repljail REPL sessions and clears the session registry. #' Useful for cleanup at the end of an LLM agent session. #' #' @param timeout numeric, timeout in seconds for each session shutdown (default: 5) @@ -512,20 +512,20 @@ replr_cleanup_sessions <- function() { #' @examples #' \dontrun{ #' # Stop all sessions at the end of analysis -#' result <- replr_stop_all_sessions() +#' result <- repljail_stop_all_sessions() #' cat("Stopped", result$data$stopped_count, "sessions") #' } -replr_stop_all_sessions <- function(timeout = 5) { +repljail_stop_all_sessions <- function(timeout = 5) { tryCatch( { - session_ids <- ls(envir = .replr_sessions) + session_ids <- ls(envir = .repljail_sessions) stopped_count <- 0 errors <- character(0) for (session_id in session_ids) { tryCatch( { - session <- get(session_id, envir = .replr_sessions) + session <- get(session_id, envir = .repljail_sessions) session$stop(timeout = timeout) stopped_count <- stopped_count + 1 }, @@ -536,7 +536,7 @@ replr_stop_all_sessions <- function(timeout = 5) { } # Clear the entire registry - rm(list = ls(envir = .replr_sessions), envir = .replr_sessions) + rm(list = ls(envir = .repljail_sessions), envir = .repljail_sessions) list( success = length(errors) == 0, @@ -573,18 +573,18 @@ replr_stop_all_sessions <- function(timeout = 5) { #' @examples #' \dontrun{ #' # Execute simple arithmetic -#' result <- replr_run_r_code("2 + 2") +#' result <- repljail_run_r_code("2 + 2") #' if (result$success) { #' cat("Output:", result$data$output) #' } #' #' # Execute more complex code -#' result <- replr_run_r_code(" +#' result <- repljail_run_r_code(" #' data <- data.frame(x = 1:5, y = letters[1:5]) #' summary(data) #' ") #' } -replr_run_r_code <- function(code, timeout = 30) { +repljail_run_r_code <- function(code, timeout = 30) { session_result <- NULL session_id <- NULL @@ -597,7 +597,7 @@ replr_run_r_code <- function(code, timeout = 30) { tryCatch( { # Create a temporary session - session_result <- replr_create_repl_session(timeout = session_timeout) + session_result <- repljail_create_repl_session(timeout = session_timeout) if (!session_result$success) { return(list( @@ -611,7 +611,7 @@ replr_run_r_code <- function(code, timeout = 30) { session_id <- session_result$data$session_id # Execute the code - exec_result <- replr_execute_code(session_id, code, timeout = timeout) + exec_result <- repljail_execute_code(session_id, code, timeout = timeout) # Return the execution result exec_result @@ -629,7 +629,7 @@ replr_run_r_code <- function(code, timeout = 30) { if (!is.null(session_id)) { tryCatch( { - replr_stop_session(session_id, timeout = cleanup_timeout) + repljail_stop_session(session_id, timeout = cleanup_timeout) }, error = function(e) { # Silent cleanup - session may already be dead @@ -641,7 +641,7 @@ replr_run_r_code <- function(code, timeout = 30) { } # ellmer Tool Definitions -# These tools wrap the replr functions to provide a standardized interface for LLM agents +# These tools wrap the repljail functions to provide a standardized interface for LLM agents #' Create REPL Session Tool Definition #' @@ -655,16 +655,16 @@ replr_run_r_code <- function(code, timeout = 30) { #' @examples #' \dontrun{ #' # Get the tool definition -#' create_tool <- replr_create_repl_session_tool() +#' create_tool <- repljail_create_repl_session_tool() #' print(create_tool$name) #' print(create_tool$description) #' } -replr_create_repl_session_tool <- function() { +repljail_create_repl_session_tool <- function() { # Try to use ellmer::tool if available, otherwise return a basic structure if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_create_repl_session, - name = "replr_create_repl_session", + repljail_create_repl_session, + name = "repljail_create_repl_session", description = "Create a new isolated R REPL session for executing R code", arguments = list( timeout = ellmer::type_number( @@ -676,7 +676,7 @@ replr_create_repl_session_tool <- function() { } else { # Fallback structure if ellmer is not available list( - name = "replr_create_repl_session", + name = "repljail_create_repl_session", description = "Create a new isolated R REPL session for executing R code", parameters = list( timeout = list( @@ -685,7 +685,7 @@ replr_create_repl_session_tool <- function() { default = 10 ) ), - fn = replr_create_repl_session + fn = repljail_create_repl_session ) } } @@ -702,14 +702,14 @@ replr_create_repl_session_tool <- function() { #' @examples #' \dontrun{ #' # Get the tool definition -#' execute_tool <- replr_execute_code_tool() +#' execute_tool <- repljail_execute_code_tool() #' print(execute_tool$name) #' } -replr_execute_code_tool <- function() { +repljail_execute_code_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_execute_code, - name = "replr_execute_code", + repljail_execute_code, + name = "repljail_execute_code", description = paste0( "Execute R code in an isolated REPL session and return structured results.", " When creating plots in the code, they will be saved as temporary PNG files ", @@ -732,7 +732,7 @@ replr_execute_code_tool <- function() { ) } else { list( - name = "replr_execute_code", + name = "repljail_execute_code", description = paste0( "Execute R code in an isolated REPL session and return structured results.", " When creating plots in the code, they will be saved as temporary PNG files ", @@ -755,7 +755,7 @@ replr_execute_code_tool <- function() { default = 30 ) ), - fn = replr_execute_code + fn = repljail_execute_code ) } } @@ -772,14 +772,14 @@ replr_execute_code_tool <- function() { #' @examples #' \dontrun{ #' # Get the tool definition -#' info_tool <- replr_get_session_info_tool() +#' info_tool <- repljail_get_session_info_tool() #' print(info_tool$description) #' } -replr_get_session_info_tool <- function() { +repljail_get_session_info_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_get_session_info, - name = "replr_get_session_info", + repljail_get_session_info, + name = "repljail_get_session_info", description = "Get detailed information about a REPL session including status and process info", arguments = list( session_id = ellmer::type_string( @@ -790,7 +790,7 @@ replr_get_session_info_tool <- function() { ) } else { list( - name = "replr_get_session_info", + name = "repljail_get_session_info", description = "Get detailed information about a REPL session including status and process info", parameters = list( session_id = list( @@ -799,7 +799,7 @@ replr_get_session_info_tool <- function() { required = TRUE ) ), - fn = replr_get_session_info + fn = repljail_get_session_info ) } } @@ -816,23 +816,23 @@ replr_get_session_info_tool <- function() { #' @examples #' \dontrun{ #' # Get the tool definition -#' list_tool <- replr_list_sessions_tool() +#' list_tool <- repljail_list_sessions_tool() #' print(list_tool$name) #' } -replr_list_sessions_tool <- function() { +repljail_list_sessions_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_list_sessions, - name = "replr_list_sessions", + repljail_list_sessions, + name = "repljail_list_sessions", description = "List all active REPL sessions with their status and information", arguments = list() ) } else { list( - name = "replr_list_sessions", + name = "repljail_list_sessions", description = "List all active REPL sessions with their status and information", parameters = list(), - fn = replr_list_sessions + fn = repljail_list_sessions ) } } @@ -849,14 +849,14 @@ replr_list_sessions_tool <- function() { #' @examples #' \dontrun{ #' # Get the tool definition -#' stop_tool <- replr_stop_session_tool() +#' stop_tool <- repljail_stop_session_tool() #' print(stop_tool$description) #' } -replr_stop_session_tool <- function() { +repljail_stop_session_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_stop_session, - name = "replr_stop_session", + repljail_stop_session, + name = "repljail_stop_session", description = "Stop a specific REPL session and remove it from the registry", arguments = list( session_id = ellmer::type_string( @@ -871,7 +871,7 @@ replr_stop_session_tool <- function() { ) } else { list( - name = "replr_stop_session", + name = "repljail_stop_session", description = "Stop a specific REPL session and remove it from the registry", parameters = list( session_id = list( @@ -885,7 +885,7 @@ replr_stop_session_tool <- function() { default = 5 ) ), - fn = replr_stop_session + fn = repljail_stop_session ) } } @@ -902,23 +902,23 @@ replr_stop_session_tool <- function() { #' @examples #' \dontrun{ #' # Get the tool definition -#' cleanup_tool <- replr_cleanup_sessions_tool() +#' cleanup_tool <- repljail_cleanup_sessions_tool() #' print(cleanup_tool$name) #' } -replr_cleanup_sessions_tool <- function() { +repljail_cleanup_sessions_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_cleanup_sessions, - name = "replr_cleanup_sessions", + repljail_cleanup_sessions, + name = "repljail_cleanup_sessions", description = "Remove dead sessions from the registry to clean up resources", arguments = list() ) } else { list( - name = "replr_cleanup_sessions", + name = "repljail_cleanup_sessions", description = "Remove dead sessions from the registry to clean up resources", parameters = list(), - fn = replr_cleanup_sessions + fn = repljail_cleanup_sessions ) } } @@ -935,14 +935,14 @@ replr_cleanup_sessions_tool <- function() { #' @examples #' \dontrun{ #' # Get the tool definition -#' stop_all_tool <- replr_stop_all_sessions_tool() +#' stop_all_tool <- repljail_stop_all_sessions_tool() #' print(stop_all_tool$description) #' } -replr_stop_all_sessions_tool <- function() { +repljail_stop_all_sessions_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_stop_all_sessions, - name = "replr_stop_all_sessions", + repljail_stop_all_sessions, + name = "repljail_stop_all_sessions", description = "Stop all active REPL sessions and clear the session registry", arguments = list( timeout = ellmer::type_number( @@ -953,7 +953,7 @@ replr_stop_all_sessions_tool <- function() { ) } else { list( - name = "replr_stop_all_sessions", + name = "repljail_stop_all_sessions", description = "Stop all active REPL sessions and clear the session registry", parameters = list( timeout = list( @@ -962,7 +962,7 @@ replr_stop_all_sessions_tool <- function() { default = 5 ) ), - fn = replr_stop_all_sessions + fn = repljail_stop_all_sessions ) } } @@ -979,23 +979,23 @@ replr_stop_all_sessions_tool <- function() { #' @examples #' \dontrun{ #' # Get the tool definition -#' run_tool <- replr_run_r_code_tool() +#' run_tool <- repljail_run_r_code_tool() #' print(run_tool$name) #' print(run_tool$description) #' } -replr_run_r_code_tool <- function() { +repljail_run_r_code_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_run_r_code, - name = "replr_run_r_code", + repljail_run_r_code, + name = "repljail_run_r_code", description = paste0( "Execute R code in a temporary isolated REPL session. ", "The session is automatically created and cleaned up after execution. ", "This is a simple interface for one-off R code execution without manual session management. ", "When creating plots in the code, they will be saved as temporary PNG files ", "and returned as file paths. Note: temp files will be deleted when the function returns, ", - "so this tool is not suitable for plot generation. Use replr_create_repl_session and ", - "replr_execute_code for persistent sessions with plot support." + "so this tool is not suitable for plot generation. Use repljail_create_repl_session and ", + "repljail_execute_code for persistent sessions with plot support." ), arguments = list( code = ellmer::type_string( @@ -1010,15 +1010,15 @@ replr_run_r_code_tool <- function() { ) } else { list( - name = "replr_run_r_code", + name = "repljail_run_r_code", description = paste0( "Execute R code in a temporary isolated REPL session. ", "The session is automatically created and cleaned up after execution. ", "This is a simple interface for one-off R code execution without manual session management. ", "When creating plots in the code, they will be saved as temporary PNG files ", "and returned as file paths. Note: temp files will be deleted when the function returns, ", - "so this tool is not suitable for plot generation. Use replr_create_repl_session and ", - "replr_execute_code for persistent sessions with plot support." + "so this tool is not suitable for plot generation. Use repljail_create_repl_session and ", + "repljail_execute_code for persistent sessions with plot support." ), parameters = list( code = list( @@ -1032,7 +1032,7 @@ replr_run_r_code_tool <- function() { default = 30 ) ), - fn = replr_run_r_code + fn = repljail_run_r_code ) } } @@ -1049,18 +1049,18 @@ replr_run_r_code_tool <- function() { #' @examples #' \dontrun{ #' # Check valid code -#' result <- replr_check_syntax("x <- 1 + 2\nprint(x)") +#' result <- repljail_check_syntax("x <- 1 + 2\nprint(x)") #' if (result$success) { #' cat("Valid syntax with", result$data$expression_count, "expressions\n") #' } #' #' # Check invalid code -#' result <- replr_check_syntax("x <- mean(c(1, 2, 3)") +#' result <- repljail_check_syntax("x <- mean(c(1, 2, 3)") #' if (!result$success) { #' cat("Syntax error:", result$error, "\n") #' } #' } -replr_check_syntax <- function(code) { +repljail_check_syntax <- function(code) { tryCatch( { # Parse the code without executing it @@ -1104,15 +1104,15 @@ replr_check_syntax <- function(code) { #' @examples #' \dontrun{ #' # Get the tool definition -#' syntax_tool <- replr_check_syntax_tool() +#' syntax_tool <- repljail_check_syntax_tool() #' print(syntax_tool$name) #' print(syntax_tool$description) #' } -replr_check_syntax_tool <- function() { +repljail_check_syntax_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_check_syntax, - name = "replr_check_syntax", + repljail_check_syntax, + name = "repljail_check_syntax", description = paste0( "Check R code for syntax errors without executing it. ", "This is useful for validating code before execution, ", @@ -1128,7 +1128,7 @@ replr_check_syntax_tool <- function() { ) } else { list( - name = "replr_check_syntax", + name = "repljail_check_syntax", description = paste0( "Check R code for syntax errors without executing it. ", "This is useful for validating code before execution, ", @@ -1142,7 +1142,7 @@ replr_check_syntax_tool <- function() { required = TRUE ) ), - fn = replr_check_syntax + fn = repljail_check_syntax ) } } @@ -1162,18 +1162,18 @@ replr_check_syntax_tool <- function() { #' @examples #' \dontrun{ #' # Lint simple code -#' result <- replr_lint_code("x = 1") +#' result <- repljail_lint_code("x = 1") #' if (result$success) { #' print(result$data$lints) #' } #' #' # Lint code with specific linters -#' result <- replr_lint_code( +#' result <- repljail_lint_code( #' "my_var <- 1", #' linters = c("object_name_linter", "line_length_linter") #' ) #' } -replr_lint_code <- function(code, linters = NULL) { +repljail_lint_code <- function(code, linters = NULL) { tryCatch( { # Check if lintr is available @@ -1265,15 +1265,15 @@ replr_lint_code <- function(code, linters = NULL) { #' @examples #' \dontrun{ #' # Get the tool definition -#' lint_tool <- replr_lint_code_tool() +#' lint_tool <- repljail_lint_code_tool() #' print(lint_tool$name) #' print(lint_tool$description) #' } -replr_lint_code_tool <- function() { +repljail_lint_code_tool <- function() { if (requireNamespace("ellmer", quietly = TRUE)) { ellmer::tool( - replr_lint_code, - name = "replr_lint_code", + repljail_lint_code, + name = "repljail_lint_code", description = paste0( "Analyze R code for style issues and potential problems using lintr without executing it. ", "This is useful for checking code quality, identifying potential issues, ", @@ -1286,14 +1286,15 @@ replr_lint_code_tool <- function() { required = TRUE ), linters = ellmer::type_array( - "Optional list of specific linter names to use (e.g., ['line_length_linter', 'object_name_linter']). If not provided, uses default linters.", + items = ellmer::type_string(), + description = "Optional list of specific linter names to use (e.g., ['line_length_linter', 'object_name_linter']). If not provided, uses default linters.", required = FALSE ) ) ) } else { list( - name = "replr_lint_code", + name = "repljail_lint_code", description = paste0( "Analyze R code for style issues and potential problems using lintr without executing it. ", "This is useful for checking code quality, identifying potential issues, ", @@ -1312,7 +1313,7 @@ replr_lint_code_tool <- function() { required = FALSE ) ), - fn = replr_lint_code + fn = repljail_lint_code ) } } diff --git a/R/replr-package.R b/R/replr-package.R index 1275f7c..fb1d9a7 100644 --- a/R/replr-package.R +++ b/R/replr-package.R @@ -1,4 +1,4 @@ -#' replr: Isolated REPL functionality for R +#' repljail: Isolated REPL functionality for R #' #' Provides isolated REPL functionality for R, allowing users to execute R code #' in a separate environment using worker processes for security and stability. @@ -17,7 +17,7 @@ #' \item Configurable timeouts and resource limits #' } #' -#' @name replr +#' @name repljail #' @importFrom nanonext socket #' @importFrom processx process #' @importFrom R6 R6Class diff --git a/R/session.R b/R/session.R index 5739363..c72ab7b 100644 --- a/R/session.R +++ b/R/session.R @@ -2,7 +2,7 @@ #' #' An R6 class that provides object-oriented interface for managing isolated #' R worker processes. This class provides automatic resource management -#' through finalizers. Docker usage is controlled by the 'replr.use.docker' option. +#' through finalizers. Worker isolation type is controlled by the 'repljail.worker.type' option. #' #' @section Constructor: #' \code{RREPLSession$new(port = NULL, timeout = 10)} @@ -171,7 +171,7 @@ RREPLSession <- R6::R6Class( return(character(0)) } - return(replr:::get_worker_debug_logs(private$.worker_info)) # nolint + return(repljail:::get_worker_debug_logs(private$.worker_info)) # nolint }, #' @description diff --git a/R/utils.R b/R/utils.R index a67a44c..ef51efa 100644 --- a/R/utils.R +++ b/R/utils.R @@ -134,7 +134,7 @@ release_port <- function(port) { get_worker_script_path <- function() { possible_paths <- c( here::here("inst", "worker.R"), - file.path(system.file(package = "replr"), "worker.R") + file.path(system.file(package = "repljail"), "worker.R") ) for (path in possible_paths) { @@ -154,7 +154,7 @@ get_worker_script_path <- function() { #' @keywords internal get_ipc_socket_path <- function() { # Create a unique socket path in the temp directory - socket_path <- tempfile(pattern = "replr_socket_", tmpdir = tempdir()) + socket_path <- tempfile(pattern = "repljail_socket_", tmpdir = tempdir()) # Ensure path is absolute and normalized normalizePath(socket_path, mustWork = FALSE) } @@ -162,8 +162,8 @@ get_ipc_socket_path <- function() { #' Start Worker Process #' #' Spawn a worker R process using processx that runs the worker script. -#' Can use different isolation strategies: native, Docker, or firejail. -#' Isolation method is controlled by options: 'replr.use.firejail' or 'replr.use.docker'. +#' Can use different isolation strategies: native, Docker, firejail, or macOS sandbox. +#' Isolation method is controlled by the 'repljail.worker.type' option. #' #' @param port integer, port number for the worker to listen on #' @param timeout numeric, timeout in seconds to wait for worker startup @@ -646,11 +646,11 @@ is_docker_available <- function() { #' Get Docker Image Name for Worker #' #' Get the Docker image name to use for worker containers. -#' Reads from option 'replr.worker.docker.image' if set, otherwise uses default. +#' Reads from option 'repljail.worker.docker.image' if set, otherwise uses default. #' #' @return character, Docker image name get_worker_docker_image <- function() { - getOption("replr.worker.docker.image", default = "replr-worker:latest") + getOption("repljail.worker.docker.image", default = "repljail-worker:latest") } #' Start Docker Worker Process @@ -671,12 +671,12 @@ start_docker_worker <- function(port, worker_script, worker_args, timeout) { } # Get configurable resource limits - memory_limit <- getOption("replr.worker.docker.memory", default = "512m") - cpu_limit <- getOption("replr.worker.docker.cpus", default = "1.0") + memory_limit <- getOption("repljail.worker.docker.memory", default = "512m") + cpu_limit <- getOption("repljail.worker.docker.cpus", default = "1.0") # Generate a unique container name for cleanup tracking container_name <- paste0( - "replr-worker-", + "repljail-worker-", port, "-", format(Sys.time(), "%Y%m%d-%H%M%S") @@ -684,7 +684,7 @@ start_docker_worker <- function(port, worker_script, worker_args, timeout) { # Check if network isolation is enabled use_network_isolation <- getOption( - "replr.worker.docker.network.isolation", + "repljail.worker.docker.network.isolation", default = FALSE ) network_name <- NULL @@ -692,7 +692,7 @@ start_docker_worker <- function(port, worker_script, worker_args, timeout) { # Create isolated network if enabled if (use_network_isolation) { network_name <- paste0( - "replr-network-", + "repljail-network-", port, "-", format(Sys.time(), "%Y%m%d-%H%M%S") @@ -713,7 +713,7 @@ start_docker_worker <- function(port, worker_script, worker_args, timeout) { container_name, # Name for cleanup "--rm", # Remove container when done "--user", - "replr", # Run as non-root user + "repljail", # Run as non-root user "--memory", memory_limit, # Memory limit (configurable) "--cpus", @@ -784,7 +784,7 @@ start_docker_worker <- function(port, worker_script, worker_args, timeout) { # Start gateway container with socat for port forwarding gateway_name <- paste0( - "replr-gateway-", + "repljail-gateway-", port, "-", format(Sys.time(), "%Y%m%d-%H%M%S") @@ -802,6 +802,8 @@ start_docker_worker <- function(port, worker_script, worker_args, timeout) { ) debug_log(paste0("Socat command: ", socat_command)) + # Use alpine/socat image pinned by SHA256 digest for supply chain security + # (alpine/socat:latest as of 2025-11-25) gateway_args <- c( "run", "-d", # Detached mode @@ -810,7 +812,7 @@ start_docker_worker <- function(port, worker_script, worker_args, timeout) { "--rm", # Auto-remove when stopped "-p", sprintf("127.0.0.1:%i:8080", port), # Map host port to gateway - "alpine/socat", + "alpine/socat@sha256:8370cf9b250bfae9d67a5309750bfdf882ca8b7053b6c88c943d230eca92e762", socat_command ) @@ -1031,7 +1033,7 @@ build_worker_docker_image <- function(image_name) { } # Fall back to building from local Dockerfile - dockerfile_path <- file.path(system.file(package = "replr"), "Dockerfile") + dockerfile_path <- file.path(system.file(package = "repljail"), "Dockerfile") # during development, also check inst/Dockerfile and working dir if (!file.exists(dockerfile_path)) { @@ -1082,9 +1084,9 @@ build_worker_docker_image <- function(image_name) { debug_success("Docker image built successfully:", image_name) } -#' Clean up orphaned replr Docker containers +#' Clean up orphaned repljail Docker containers #' -#' Remove any leftover replr worker containers that may be running +#' Remove any leftover repljail worker containers that may be running #' #' @return logical, TRUE if cleanup was successful #' @export @@ -1096,16 +1098,16 @@ cleanup_docker_containers <- function() { tryCatch( { - debug_log("Cleaning up orphaned replr Docker containers") + debug_log("Cleaning up orphaned repljail Docker containers") - # Find all replr worker containers + # Find all repljail worker containers worker_containers <- system2( "docker", c( "ps", "-a", "--filter", - "name=replr-worker-", + "name=repljail-worker-", "--format", "{{.Names}}" ), @@ -1113,14 +1115,14 @@ cleanup_docker_containers <- function() { stderr = FALSE ) - # Find all replr gateway containers + # Find all repljail gateway containers gateway_containers <- system2( "docker", c( "ps", "-a", "--filter", - "name=replr-gateway-", + "name=repljail-gateway-", "--format", "{{.Names}}" ), @@ -1133,7 +1135,7 @@ cleanup_docker_containers <- function() { all_containers <- all_containers[nchar(all_containers) > 0] if (length(all_containers) == 0) { - debug_log("No orphaned replr containers found") + debug_log("No orphaned repljail containers found") return(TRUE) } @@ -1193,6 +1195,7 @@ create_docker_network <- function(network_name) { # The --internal flag blocks all external network access (no internet) # A gateway sidecar container bridges host-to-worker communication # Note: We do NOT use enable_icc=false because the gateway MUST communicate with the worker + # Note: We let Docker auto-assign the subnet to avoid conflicts with existing networks result <- system2( "docker", c( @@ -1201,8 +1204,6 @@ create_docker_network <- function(network_name) { "--driver", "bridge", "--internal", # Block all external access (no internet) - "--subnet", - "172.28.0.0/16", # Custom subnet to avoid conflicts network_name ), stdout = TRUE, @@ -1267,7 +1268,7 @@ remove_docker_network <- function(network_name) { #' Cleanup Orphaned Docker Networks #' -#' Remove orphaned replr Docker networks that may have been left behind +#' Remove orphaned repljail Docker networks that may have been left behind #' #' @return logical, TRUE if cleanup was successful #' @export @@ -1279,16 +1280,16 @@ cleanup_docker_networks <- function() { tryCatch( { - debug_log("Cleaning up orphaned replr Docker networks") + debug_log("Cleaning up orphaned repljail Docker networks") - # Find all replr worker networks + # Find all repljail worker networks networks <- system2( "docker", c( "network", "ls", "--filter", - "name=replr-network-", + "name=repljail-network-", "--format", "{{.Name}}" ), @@ -1302,7 +1303,7 @@ cleanup_docker_networks <- function() { attr(networks, "status") == 0 ) { # No networks found or command failed - debug_log("No orphaned replr networks found") + debug_log("No orphaned repljail networks found") return(TRUE) } @@ -1326,7 +1327,7 @@ cleanup_docker_networks <- function() { ) return(TRUE) } else { - debug_log("No orphaned replr networks found") + debug_log("No orphaned repljail networks found") return(TRUE) } }, diff --git a/R/worker-wrappers.R b/R/worker-wrappers.R index a3fcfe4..654a4b3 100644 --- a/R/worker-wrappers.R +++ b/R/worker-wrappers.R @@ -137,12 +137,12 @@ DockerWorkerWrapper <- R6::R6Class( } # Get configurable resource limits - memory_limit <- getOption("replr.worker.docker.memory", default = "512m") - cpu_limit <- getOption("replr.worker.docker.cpus", default = "1.0") + memory_limit <- getOption("repljail.worker.docker.memory", default = "512m") + cpu_limit <- getOption("repljail.worker.docker.cpus", default = "1.0") # Generate a unique container name for cleanup tracking container_name <- paste0( - "replr-worker-", + "repljail-worker-", port, "-", format(Sys.time(), "%Y%m%d-%H%M%S") @@ -150,7 +150,7 @@ DockerWorkerWrapper <- R6::R6Class( # Check if network isolation is enabled use_network_isolation <- getOption( - "replr.worker.docker.network.isolation", + "repljail.worker.docker.network.isolation", default = FALSE ) network_name <- NULL @@ -158,7 +158,7 @@ DockerWorkerWrapper <- R6::R6Class( # Create isolated network if enabled if (use_network_isolation) { network_name <- paste0( - "replr-network-", + "repljail-network-", port, "-", format(Sys.time(), "%Y%m%d-%H%M%S") @@ -179,7 +179,7 @@ DockerWorkerWrapper <- R6::R6Class( container_name, "--rm", "--user", - "replr", + "repljail", "--memory", memory_limit, "--cpus", @@ -246,7 +246,7 @@ DockerWorkerWrapper <- R6::R6Class( attr(proc, "network_name") <- network_name gateway_name <- paste0( - "replr-gateway-", + "repljail-gateway-", port, "-", format(Sys.time(), "%Y%m%d-%H%M%S") @@ -261,6 +261,8 @@ DockerWorkerWrapper <- R6::R6Class( port ) + # Use alpine/socat image pinned by SHA256 digest for supply chain security + # (alpine/socat:latest as of 2025-11-25) gateway_args <- c( "run", "-d", @@ -269,7 +271,7 @@ DockerWorkerWrapper <- R6::R6Class( "--rm", "-p", sprintf("127.0.0.1:%i:8080", port), - "alpine/socat", + "alpine/socat@sha256:8370cf9b250bfae9d67a5309750bfdf882ca8b7053b6c88c943d230eca92e762", socat_command ) @@ -401,7 +403,7 @@ FirejailWorkerWrapper <- R6::R6Class( # Get custom profile if specified custom_profile <- getOption( - "replr.worker.firejail.profile", + "repljail.worker.firejail.profile", default = NULL ) @@ -517,7 +519,7 @@ MacOSSandboxWorkerWrapper <- R6::R6Class( # Get custom profile if specified custom_profile <- getOption( - "replr.worker.macos.sandbox.profile", + "repljail.worker.macos.sandbox.profile", default = NULL ) @@ -535,7 +537,7 @@ MacOSSandboxWorkerWrapper <- R6::R6Class( # Default macOS sandbox profile using Sandbox Profile Language (SBPL) # Provides network isolation and filesystem write restrictions profile_content <- c( - "; macOS Sandbox Profile for replr worker", + "; macOS Sandbox Profile for repljail worker", "; Provides network isolation and home directory write protection", "(version 1)", "", @@ -704,35 +706,8 @@ is_firejail_available <- function() { #' @return WorkerWrapper object (NativeWorkerWrapper, DockerWorkerWrapper, FirejailWorkerWrapper, or MacOSSandboxWorkerWrapper) #' @keywords internal create_worker_wrapper <- function() { - # Get the worker type from the new unified option - worker_type <- getOption("replr.worker.type", default = "native") - - # For backward compatibility, check old boolean options if new option not set - if (worker_type == "native" && is.null(getOption("replr.worker.type"))) { - # Check legacy options in priority order: macos_sandbox > firejail > docker > native - if (isTRUE(getOption("replr.use.macos.sandbox"))) { - worker_type <- "macos-sandbox" - warning( - "Option 'replr.use.macos.sandbox' is deprecated. ", - "Please use options(replr.worker.type = \"macos-sandbox\") instead.", - call. = FALSE - ) - } else if (isTRUE(getOption("replr.use.firejail"))) { - worker_type <- "firejail" - warning( - "Option 'replr.use.firejail' is deprecated. ", - "Please use options(replr.worker.type = \"firejail\") instead.", - call. = FALSE - ) - } else if (isTRUE(getOption("replr.use.docker"))) { - worker_type <- "docker" - warning( - "Option 'replr.use.docker' is deprecated. ", - "Please use options(replr.worker.type = \"docker\") instead.", - call. = FALSE - ) - } - } + # Get the worker type from the unified option + worker_type <- getOption("repljail.worker.type", default = "native") # Validate and create the appropriate wrapper wrapper <- switch( diff --git a/README.md b/README.md index a8e7b5d..7c38065 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -# replr +# repljail **Isolated REPL functionality for R** -`replr` provides a robust system for executing R code in isolated worker processes, offering complete separation between the main R session and code execution environments. Useful for applications that need to run untrusted or potentially problematic R code without affecting the parent process. +`repljail` provides a robust system for executing R code in isolated worker processes, offering complete separation between the main R session and code execution environments. Useful for applications that need to run untrusted or potentially problematic R code without affecting the parent process. ## Installation -You can install the development version of replr like this: +You can install the development version of repljail like this: ```r install.packages("pak") -pak::pak("pkrusche/replr") +pak::pak("pkrusche/repljail") # Or using devtools: # install.packages("devtools") -# devtools::install_github("pkrusche/replr") +# devtools::install_github("pkrusche/repljail") ``` ## How It Works @@ -38,7 +38,7 @@ pak::pak("pkrusche/replr") ## Quick Start ```r -library(replr) +library(repljail) # Create a new isolated R session session <- RREPLSession$new() @@ -59,13 +59,13 @@ For detailed examples including error handling, debug logging, multiple sessions ## Docker Container Support -`replr` supports running worker processes inside Docker containers for enhanced security and isolation. When Docker is available, it automatically builds a minimal container image and runs workers with stricter security constraints. +`repljail` supports running worker processes inside Docker containers for enhanced security and isolation. When Docker is available, it automatically builds a minimal container image and runs workers with stricter security constraints. For Docker support, you need: - Docker installed and accessible - Permission to run `docker` commands -- Internet access for initial image build (pulls `rocker/r-ver:4.4`) +- Internet access for initial image build (pulls `rocker/r-ver:4.4`, pinned by SHA256 digest) The package automatically: @@ -75,13 +75,13 @@ The package automatically: ### Example ```r -library(replr) +library(repljail) # Check if Docker is available is_docker_available() # TRUE if Docker is present # Enable Docker mode explicitly -options(replr.worker.type = "docker") +options(repljail.worker.type = "docker") # Create session with Docker worker session <- RREPLSession$new(timeout = 15) # Longer timeout for Docker startup @@ -116,40 +116,40 @@ You can customize Docker worker behavior using global options: | Option | Default | Description | | --------------------------------------- | ----------------------- | ------------------------------------------------------- | -| `replr.worker.type` | `"native"` | Worker type: "native", "docker", "firejail", "macos-sandbox" | -| `replr.worker.docker.image` | `"replr-worker:latest"` | Docker image name for worker containers | -| `replr.worker.docker.memory` | `"512m"` | Memory limit for Docker containers (e.g., "1g", "256m") | -| `replr.worker.docker.cpus` | `"1.0"` | CPU limit for Docker containers (e.g., "2.0", "0.5") | -| `replr.worker.docker.network.isolation` | `FALSE` | Enable isolated Docker networks with no external access | +| `repljail.worker.type` | `"native"` | Worker type: "native", "docker", "firejail", "macos-sandbox" | +| `repljail.worker.docker.image` | `"repljail-worker:latest"` | Docker image name for worker containers | +| `repljail.worker.docker.memory` | `"512m"` | Memory limit for Docker containers (e.g., "1g", "256m") | +| `repljail.worker.docker.cpus` | `"1.0"` | CPU limit for Docker containers (e.g., "2.0", "0.5") | +| `repljail.worker.docker.network.isolation` | `FALSE` | Enable isolated Docker networks with no external access | These options apply to all Docker workers started after they are set. Changes take effect immediately for new worker processes. ```r -# Configure Docker image name (default: "replr-worker:latest") -options(replr.worker.docker.image = "my-custom-r-image:v1.0") +# Configure Docker image name (default: "repljail-worker:latest") +options(repljail.worker.docker.image = "my-custom-r-image:v1.0") # Configure memory limit (default: "512m") -options(replr.worker.docker.memory = "1g") # 1GB memory -options(replr.worker.docker.memory = "256m") # 256MB memory +options(repljail.worker.docker.memory = "1g") # 1GB memory +options(repljail.worker.docker.memory = "256m") # 256MB memory # Configure CPU limit (default: "1.0") -options(replr.worker.docker.cpus = "2.0") # 2 CPU cores -options(replr.worker.docker.cpus = "0.5") # Half a CPU core +options(repljail.worker.docker.cpus = "2.0") # 2 CPU cores +options(repljail.worker.docker.cpus = "0.5") # Half a CPU core # Reset to defaults -options(replr.worker.docker.image = NULL) -options(replr.worker.docker.memory = NULL) -options(replr.worker.docker.cpus = NULL) +options(repljail.worker.docker.image = NULL) +options(repljail.worker.docker.memory = NULL) +options(repljail.worker.docker.cpus = NULL) # Example: Configure for high-performance workloads options( - replr.worker.docker.memory = "2g", - replr.worker.docker.cpus = "4.0" + repljail.worker.docker.memory = "2g", + repljail.worker.docker.cpus = "4.0" ) # Enable network isolation options( - replr.worker.docker.network.isolation = TRUE + repljail.worker.docker.network.isolation = TRUE ) # Start session with custom settings @@ -163,7 +163,7 @@ Lightweight process isolation using firejail for Linux systems. Requires `fireja ```r # Check availability and enable is_firejail_available() -options(replr.worker.type = "firejail") +options(repljail.worker.type = "firejail") # Create sandboxed session session <- RREPLSession$new() @@ -173,7 +173,7 @@ session$stop() **Default Security**: Network isolation (`--net=lo`), private temp directory, capability dropping, seccomp filtering, no privilege escalation. -**Custom Profiles**: Set `replr.worker.firejail.profile` to path of custom `.profile` file. See `inst/examples/` for demonstrations. +**Custom Profiles**: Set `repljail.worker.firejail.profile` to path of custom `.profile` file. See `inst/examples/` for demonstrations. ## macOS Sandbox Support @@ -182,7 +182,7 @@ Native sandboxing using `sandbox-exec` (pre-installed on macOS). Uses Sandbox Pr ```r # Check availability and enable is_macos_sandbox_available() -options(replr.worker.type = "macos-sandbox") +options(repljail.worker.type = "macos-sandbox") # Create sandboxed session session <- RREPLSession$new() @@ -192,7 +192,7 @@ session$stop() **Default Security**: Filesystem isolation (read-only system, write to `/tmp` only), network restricted to localhost, IPC restrictions, system call filtering. -**Custom Profiles**: Set `replr.worker.macos.sandbox.profile` to path of custom `.sb` file. See `man sandbox-exec` and `inst/examples/macos-sandbox-demo.R`. +**Custom Profiles**: Set `repljail.worker.macos.sandbox.profile` to path of custom `.sb` file. See `man sandbox-exec` and `inst/examples/macos-sandbox-demo.R`. ## Security Comparison @@ -219,22 +219,22 @@ session$stop() ```r # Development -options(replr.worker.type = "native") +options(repljail.worker.type = "native") # Platform-specific sandboxing -options(replr.worker.type = "macos-sandbox") # macOS -options(replr.worker.type = "firejail") # Linux +options(repljail.worker.type = "macos-sandbox") # macOS +options(repljail.worker.type = "firejail") # Linux # Maximum security (any platform) -options(replr.worker.type = "docker") -options(replr.worker.docker.network.isolation = TRUE) +options(repljail.worker.type = "docker") +options(repljail.worker.docker.network.isolation = TRUE) session <- RREPLSession$new() ``` ## ellmer Tools for LLM Agents -`replr` includes specialized tools designed for the [ellmer](https://ellmer.tidyverse.org/) package, allowing LLM agents to easily create and manage isolated R REPL sessions. These tools provide a standardized interface with structured responses optimized for LLM consumption. +`repljail` includes specialized tools designed for the [ellmer](https://ellmer.tidyverse.org/) package, allowing LLM agents to easily create and manage isolated R REPL sessions. These tools provide a standardized interface with structured responses optimized for LLM consumption. ### Available Tools @@ -244,49 +244,49 @@ The package includes several demonstration scripts in `inst/examples/` for this. #### Simple One-Off Execution & R Syntax Checking -- **replr_check_syntax()** - Check R code syntax without execution (safe validation) -- **replr_run_r_code()** - One-off code execution with automatic cleanup -- **replr_lint_code()** - Analyze code for style issues without executing it +- **repljail_check_syntax()** - Check R code syntax without execution (safe validation) +- **repljail_run_r_code()** - One-off code execution with automatic cleanup +- **repljail_lint_code()** - Analyze code for style issues without executing it #### Full Session Management -- **replr_create_repl_session()** - Create isolated R sessions -- **replr_execute_code()** - Execute R code in a session -- **replr_get_session_info()** - Get session status and details -- **replr_list_sessions()** - List all active sessions -- **replr_stop_session()** - Stop a specific session -- **replr_cleanup_sessions()** - Remove dead sessions -- **replr_stop_all_sessions()** - Stop all active sessions +- **repljail_create_repl_session()** - Create isolated R sessions +- **repljail_execute_code()** - Execute R code in a session +- **repljail_get_session_info()** - Get session status and details +- **repljail_list_sessions()** - List all active sessions +- **repljail_stop_session()** - Stop a specific session +- **repljail_cleanup_sessions()** - Remove dead sessions +- **repljail_stop_all_sessions()** - Stop all active sessions ### LLM Agent Demo (`llm-agent-demo.R`) -Complete demonstration of an LLM agent performing data analysis using replr tools. +Complete demonstration of an LLM agent performing data analysis using repljail tools. ```r # Install requirements -install.packages(c("replr", "ellmer")) +install.packages(c("repljail", "ellmer")) # Set your API key (required) Sys.setenv(OPENAI_API_KEY = "your-api-key-here") # Run the demo -source(system.file("examples", "llm-agent-demo.R", package = "replr")) +source(system.file("examples", "llm-agent-demo.R", package = "repljail")) ``` The demo shows: -1. Initializing an OpenAI chat session with replr tools -2. Registering all replr tools with the LLM agent +1. Initializing an OpenAI chat session with repljail tools +2. Registering all repljail tools with the LLM agent 3. Sending a data analysis task to the agent 4. Watching the agent automatically create sessions, execute code, and clean up 5. Displaying the complete analysis results and tool usage ### Agentic Coding Evaluation (`agentic-coding.R`) -Compares LLM performance with and without replr tool access: +Compares LLM performance with and without repljail tool access: ```r -source(system.file("examples", "agentic-coding.R", package = "replr")) +source(system.file("examples", "agentic-coding.R", package = "repljail")) ``` Features: diff --git a/TODO.md b/TODO.md index cb234a3..9321666 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -# Remaining Implementation Tasks for replr +# Remaining Implementation Tasks for repljail - [ ] Add automatic worker restart logic with exponential backoff (advanced feature) - [ ] Implement memory usage monitoring with pryr diff --git a/inst/Dockerfile b/inst/Dockerfile index a172193..ee3d7a6 100644 --- a/inst/Dockerfile +++ b/inst/Dockerfile @@ -1,6 +1,7 @@ -# Minimal Dockerfile for replr worker containers +# Minimal Dockerfile for repljail worker containers # Based on R 4.4 with security hardening -FROM rocker/r-ver:4.4 +# Image pinned by SHA256 digest for supply chain security (rocker/r-ver:4.4 as of 2025-11-25) +FROM rocker/r-ver@sha256:80bf33ee1b61f3252129912867abf23339ef1d9e96c837cbd86341a23c3fef07 # Install minimal system dependencies RUN apt-get update && apt-get install -y \ @@ -29,16 +30,16 @@ RUN Rscript -e \ "install.packages(c('nanonext', 'evaluate', 'cli'), repos='https://cran.rstudio.com/', dependencies=TRUE)" # Security hardening: create non-root user -RUN groupadd -r replr && useradd -r -g replr -m -d /home/replr replr +RUN groupadd -r repljail && useradd -r -g repljail -m -d /home/repljail repljail # Create working directory and set ownership -RUN mkdir -p /app && chown replr:replr /app +RUN mkdir -p /app && chown repljail:repljail /app WORKDIR /app # The actual worker.R will be mounted as a volume # Switch to non-root user -USER replr +USER repljail # Set default command (will be overridden) CMD ["Rscript", "--version"] diff --git a/inst/examples/agentic-coding.R b/inst/examples/agentic-coding.R index bff7a88..5111de0 100644 --- a/inst/examples/agentic-coding.R +++ b/inst/examples/agentic-coding.R @@ -1,7 +1,7 @@ library(ellmer) library(tibble) library(dplyr) -library(replr) +library(repljail) # Simple task in the format of {vitals} # Vitals cannot visualize traces with tools yet, so we just evaluate directly. @@ -36,7 +36,7 @@ vanilla_chat <- chat_anthropic( ) ) -replr_augmented_chat <- chat_anthropic( +repljail_augmented_chat <- chat_anthropic( model = "claude-sonnet-4-20250514", system_prompt = paste( "You are an expert R programmer and data analyst.", @@ -49,24 +49,24 @@ replr_augmented_chat <- chat_anthropic( "Be concise and give a minimal answer that is correct. " ) ) -# Register replr tools with the chat -cat("Registering replr tools...\n") +# Register repljail tools with the chat +cat("Registering repljail tools...\n") # Run in Docker for isolation -options(replr.use.docker = TRUE) -# Get all replr tool functions +options(repljail.worker.type = "docker") +# Get all repljail tool functions tools <- list( - replr_create_repl_session_tool(), - replr_execute_code_tool(), - replr_get_session_info_tool(), - replr_list_sessions_tool(), - replr_stop_session_tool(), - replr_cleanup_sessions_tool(), - replr_stop_all_sessions_tool() + repljail_create_repl_session_tool(), + repljail_execute_code_tool(), + repljail_get_session_info_tool(), + repljail_list_sessions_tool(), + repljail_stop_session_tool(), + repljail_cleanup_sessions_tool(), + repljail_stop_all_sessions_tool() ) # Register each tool with the chat for (tool in tools) { - replr_augmented_chat$register_tool(tool) + repljail_augmented_chat$register_tool(tool) cat(" ✓ Registered:", tool@name, "\n") } @@ -85,9 +85,9 @@ for (i in seq_len(nrow(tasks))) { cat("Getting vanilla response...\n") vanilla_response <- vanilla_chat$clone()$chat(tasks$input[i], echo = FALSE) - # Evaluate with replr-augmented chat - cat("Getting replr-augmented response...\n") - replr_response <- replr_augmented_chat$clone()$chat( + # Evaluate with repljail-augmented chat + cat("Getting repljail-augmented response...\n") + repljail_response <- repljail_augmented_chat$clone()$chat( tasks$input[i], echo = FALSE ) @@ -105,15 +105,15 @@ for (i in seq_len(nrow(tasks))) { echo = FALSE ) - # Judge replr response - judge_replr <- judge_chat$clone()$chat( + # Judge repljail response + judge_repljail <- judge_chat$clone()$chat( paste( "Question:", tasks$input[i], "Target Answer:", tasks$target[i], "Response:", - replr_response + repljail_response ), echo = FALSE ) @@ -126,9 +126,9 @@ for (i in seq_len(nrow(tasks))) { input = tasks$input[i], target = tasks$target[i], vanilla_response = vanilla_response, - replr_response = replr_response, + repljail_response = repljail_response, vanilla_correct = grepl("Correct", judge_vanilla, ignore.case = TRUE), - replr_correct = grepl("Correct", judge_replr, ignore.case = TRUE) + repljail_correct = grepl("Correct", judge_repljail, ignore.case = TRUE) ) ) @@ -140,10 +140,10 @@ for (i in seq_len(nrow(tasks))) { "...\n" ) cat( - "Replr response (", - judge_replr, + "repljail response (", + judge_repljail, "):", - substr(replr_response, 1, 100), + substr(repljail_response, 1, 100), "...\n" ) cat("---\n\n") diff --git a/inst/examples/conversation-logging-demo.R b/inst/examples/conversation-logging-demo.R index 72eb2d7..23b269c 100644 --- a/inst/examples/conversation-logging-demo.R +++ b/inst/examples/conversation-logging-demo.R @@ -1,7 +1,7 @@ #' Conversation Logging Demo #' #' This example demonstrates how to use the ConversationLogger to log -#' ellmer chat sessions with replr tools in markdown format. +#' ellmer chat sessions with repljail tools in markdown format. #' #' The logger captures: #' - User prompts @@ -11,7 +11,7 @@ #' #' All logs are formatted in readable markdown with code blocks for R code. -library(replr) +library(repljail) library(ellmer) # Create a log file path @@ -51,21 +51,21 @@ cat("Attaching logger to chat...\n") logger$attach(chat) cat("✓ Logger attached\n\n") -# Register replr tools with the chat -cat("Registering replr tools...\n") +# Register repljail tools with the chat +cat("Registering repljail tools...\n") # Run in Docker for isolation -options(replr.use.docker = TRUE) +options(repljail.worker.type = "docker") -# Get all replr tool functions +# Get all repljail tool functions tools <- list( - replr_create_repl_session_tool(), - replr_execute_code_tool(), - replr_get_session_info_tool(), - replr_list_sessions_tool(), - replr_stop_session_tool(), - replr_cleanup_sessions_tool(), - replr_stop_all_sessions_tool() + repljail_create_repl_session_tool(), + repljail_execute_code_tool(), + repljail_get_session_info_tool(), + repljail_list_sessions_tool(), + repljail_stop_session_tool(), + repljail_cleanup_sessions_tool(), + repljail_stop_all_sessions_tool() ) # Register each tool with the chat @@ -116,12 +116,12 @@ cat("Log file size:", file_size, "bytes\n\n") # Clean up any remaining sessions cat("=== Final Cleanup ===\n") -final_sessions <- replr_list_sessions() +final_sessions <- repljail_list_sessions() if (final_sessions$success && final_sessions$data$count == 0) { cat("✓ All sessions cleaned up successfully\n") } else { cat("Cleaning up remaining sessions...\n") - cleanup_result <- replr_stop_all_sessions() + cleanup_result <- repljail_stop_all_sessions() if (cleanup_result$success) { cat("✓ All sessions stopped\n") } diff --git a/inst/examples/lint-demo.R b/inst/examples/lint-demo.R index a52e873..3280e8c 100644 --- a/inst/examples/lint-demo.R +++ b/inst/examples/lint-demo.R @@ -1,11 +1,11 @@ #!/usr/bin/env Rscript -# Example demonstrating the replr_lint_code MCP tool +# Example demonstrating the repljail_lint_code MCP tool # This script shows how to use the lintr functionality without executing code -library(replr) +library(repljail) cat("========================================\n") -cat("replr_lint_code MCP Tool Demo\n") +cat("repljail_lint_code MCP Tool Demo\n") cat("========================================\n\n") # Example 1: Clean code (no issues) @@ -18,7 +18,7 @@ z <- x + y print(z) " -result1 <- replr_lint_code(clean_code) +result1 <- repljail_lint_code(clean_code) cat("Code to lint:\n", clean_code, "\n") cat("Success:", result1$success, "\n") cat("Message:", result1$message, "\n") @@ -33,7 +33,7 @@ y=2 z <- x + y " -result2 <- replr_lint_code(bad_code) +result2 <- repljail_lint_code(bad_code) cat("Code to lint:\n", bad_code, "\n") cat("Success:", result2$success, "\n") cat("Message:", result2$message, "\n") @@ -57,10 +57,11 @@ cat("\n") # Example 3: Using the tool definition cat("Example 3: Tool definition structure\n") cat("-------------------------------------\n") -tool <- replr_lint_code_tool() -cat("Tool name:", tool$name, "\n") -cat("Description:", substr(tool$description, 1, 80), "...\n") -cat("Parameters:", paste(names(tool$parameters), collapse = ", "), "\n\n") +tool <- repljail_lint_code_tool() +# Note: ellmer tools are S7 objects, use @ to access properties +cat("Tool name:", tool@name, "\n") +cat("Description:", substr(tool@description, 1, 80), "...\n") +cat("Input parameters:", paste(names(tool@arguments@properties), collapse = ", "), "\n\n") cat("========================================\n") cat("Demo complete!\n") diff --git a/inst/examples/llm-agent-demo.R b/inst/examples/llm-agent-demo.R index e915f19..74f1d00 100644 --- a/inst/examples/llm-agent-demo.R +++ b/inst/examples/llm-agent-demo.R @@ -4,11 +4,11 @@ #' to perform a complete data analysis workflow using isolated REPL sessions. #' The script will: #' -#' 1. Create an agent with access to the ellmer::tool() implementations from {replr} +#' 1. Create an agent with access to the ellmer::tool() implementations from {repljail} #' 2. Ask the agent to create a histogram of 100 random normal values. #' 3. Check if the computation ran successfully -library(replr) +library(repljail) library(ellmer) # Initialize chat with OpenAI (requires API key) @@ -39,21 +39,21 @@ tryCatch( plain_chat <- chat$clone() -# Register replr tools with the chat -cat("Registering replr tools...\n") +# Register repljail tools with the chat +cat("Registering repljail tools...\n") # Run in Docker for isolation -options(replr.use.docker = TRUE) +options(repljail.worker.type = "docker") -# Get all replr tool functions +# Get all repljail tool functions tools <- list( - replr_create_repl_session_tool(), - replr_execute_code_tool(), - replr_get_session_info_tool(), - replr_list_sessions_tool(), - replr_stop_session_tool(), - replr_cleanup_sessions_tool(), - replr_stop_all_sessions_tool() + repljail_create_repl_session_tool(), + repljail_execute_code_tool(), + repljail_get_session_info_tool(), + repljail_list_sessions_tool(), + repljail_stop_session_tool(), + repljail_cleanup_sessions_tool(), + repljail_stop_all_sessions_tool() ) # Register each tool with the chat @@ -102,7 +102,7 @@ if (grepl("__IMAGE_RETURNED__:", response)) { # Show current sessions (should be empty if cleanup worked) cat("\n=== Final Session Check ===\n") -final_sessions <- replr_list_sessions() +final_sessions <- repljail_list_sessions() if (final_sessions$success && final_sessions$data$count == 0) { cat("✓ All sessions cleaned up successfully\n") } else { @@ -111,7 +111,7 @@ if (final_sessions$success && final_sessions$data$count == 0) { # Clean up any remaining sessions cat("Cleaning up remaining sessions...\n") - cleanup_result <- replr_stop_all_sessions() + cleanup_result <- repljail_stop_all_sessions() if (cleanup_result$success) { cat("✓ All sessions stopped\n") } else { diff --git a/inst/examples/sandbox-capabilities-demo.R b/inst/examples/sandbox-capabilities-demo.R index 000c56e..3fc327a 100644 --- a/inst/examples/sandbox-capabilities-demo.R +++ b/inst/examples/sandbox-capabilities-demo.R @@ -1,13 +1,13 @@ #!/usr/bin/env Rscript -# Unified Sandbox Capabilities Demo for replr Package +# Unified Sandbox Capabilities Demo for repljail Package # This script checks all available sandboxing methods and tests their features -library(replr) +library(repljail) cat("\n") cat("================================================================================\n") -cat(" replr Sandbox Capabilities Demo\n") +cat(" repljail Sandbox Capabilities Demo\n") cat("================================================================================\n") cat("\n") @@ -87,13 +87,13 @@ test_sandbox_features <- function(wrapper_type) { cat(sprintf("\n--- Testing %s ---\n", toupper(wrapper_type))) # Configure worker type - options(replr.worker.type = wrapper_type) + options(repljail.worker.type = wrapper_type) # Handle network isolation for Docker if (wrapper_type == "docker") { # Test both with and without network isolation for (net_iso in c(FALSE, TRUE)) { - options(replr.worker.docker.network.isolation = net_iso) + options(repljail.worker.docker.network.isolation = net_iso) mode_name <- if (net_iso) "docker-isolated" else "docker-standard" cat(sprintf("\nMode: %s (network.isolation=%s)\n", mode_name, net_iso)) @@ -105,8 +105,8 @@ test_sandbox_features <- function(wrapper_type) { } # Reset options - options(replr.worker.type = NULL) - options(replr.worker.docker.network.isolation = NULL) + options(repljail.worker.type = NULL) + options(repljail.worker.docker.network.isolation = NULL) } #' Implementation of feature testing @@ -179,7 +179,7 @@ test_sandbox_features_impl <- function(mode_name) { ) # Test 4: Temp directory isolation (writes inside sandbox should not affect host) - temp_test_file <- tempfile(pattern = "replr_isolation_test_", fileext = ".txt") + temp_test_file <- tempfile(pattern = "repljail_isolation_test_", fileext = ".txt") temp_result <- test_feature( session, "Temp directory isolation (host should not see sandbox writes)", @@ -214,7 +214,7 @@ test_sandbox_features_impl <- function(mode_name) { mode_results$features$fs_temp_isolation <- temp_result # Test 5: Home directory isolation (writes inside sandbox should not affect host) - home_test_file <- file.path(path.expand("~"), paste0(".replr_test_", format(Sys.time(), "%Y%m%d%H%M%S"), "_", sample(1000:9999, 1))) + home_test_file <- file.path(path.expand("~"), paste0(".repljail_test_", format(Sys.time(), "%Y%m%d%H%M%S"), "_", sample(1000:9999, 1))) home_result <- test_feature( session, "Home directory isolation (host should not see sandbox writes)", @@ -507,17 +507,17 @@ cat("\n") cat("Recommendations:\n") cat("--------------------------------------------------------------------------------\n") cat("• For maximum security:\n") -cat(" - Use Docker with network isolation: options(replr.worker.type = \"docker\",\n") -cat(" replr.worker.docker.network.isolation = TRUE)\n") +cat(" - Use Docker with network isolation: options(repljail.worker.type = \"docker\",\n") +cat(" repljail.worker.docker.network.isolation = TRUE)\n") cat("\n") cat("• For lightweight Linux sandboxing:\n") -cat(" - Use Firejail: options(replr.worker.type = \"firejail\")\n") +cat(" - Use Firejail: options(repljail.worker.type = \"firejail\")\n") cat("\n") cat("• For native macOS sandboxing:\n") -cat(" - Use macOS Sandbox: options(replr.worker.type = \"macos-sandbox\")\n") +cat(" - Use macOS Sandbox: options(repljail.worker.type = \"macos-sandbox\")\n") cat("\n") cat("• For development/testing (no isolation):\n") -cat(" - Use Native: options(replr.worker.type = \"native\")\n") +cat(" - Use Native: options(repljail.worker.type = \"native\")\n") cat("\n") cat("================================================================================\n") diff --git a/inst/worker.R b/inst/worker.R index c6996de..82ae8db 100755 --- a/inst/worker.R +++ b/inst/worker.R @@ -1,6 +1,6 @@ #!/usr/bin/env Rscript -# Worker process script for replr package +# Worker process script for repljail package # This script runs in an isolated R process and executes code via evaluate package # Communication with parent process via nanonext REP socket diff --git a/man/ConversationLogger.Rd b/man/ConversationLogger.Rd index 0685b50..43404dc 100644 --- a/man/ConversationLogger.Rd +++ b/man/ConversationLogger.Rd @@ -17,7 +17,7 @@ in markdown format. It captures user prompts, assistant responses, tool calls Create a logger and attach it to an ellmer Chat object: \preformatted{ library(ellmer) -library(replr) +library(repljail) # Create a chat chat <- chat_openai() diff --git a/man/DockerWorkerWrapper.Rd b/man/DockerWorkerWrapper.Rd index 920a38c..898ed8a 100644 --- a/man/DockerWorkerWrapper.Rd +++ b/man/DockerWorkerWrapper.Rd @@ -13,7 +13,7 @@ Worker wrapper for Docker container isolation } \keyword{internal} \section{Super class}{ -\code{\link[replr:WorkerWrapper]{replr::WorkerWrapper}} -> \code{DockerWorkerWrapper} +\code{\link[repljail:WorkerWrapper]{repljail::WorkerWrapper}} -> \code{DockerWorkerWrapper} } \section{Methods}{ \subsection{Public methods}{ @@ -26,8 +26,8 @@ Worker wrapper for Docker container isolation \if{html}{\out{
Inherited methods
}} diff --git a/man/FirejailWorkerWrapper.Rd b/man/FirejailWorkerWrapper.Rd index 070eced..29cec14 100644 --- a/man/FirejailWorkerWrapper.Rd +++ b/man/FirejailWorkerWrapper.Rd @@ -13,7 +13,7 @@ Worker wrapper for firejail sandbox isolation } \keyword{internal} \section{Super class}{ -\code{\link[replr:WorkerWrapper]{replr::WorkerWrapper}} -> \code{FirejailWorkerWrapper} +\code{\link[repljail:WorkerWrapper]{repljail::WorkerWrapper}} -> \code{FirejailWorkerWrapper} } \section{Methods}{ \subsection{Public methods}{ @@ -25,9 +25,9 @@ Worker wrapper for firejail sandbox isolation \if{html}{\out{
Inherited methods
}} diff --git a/man/MacOSSandboxWorkerWrapper.Rd b/man/MacOSSandboxWorkerWrapper.Rd index 2c1f360..6ff236c 100644 --- a/man/MacOSSandboxWorkerWrapper.Rd +++ b/man/MacOSSandboxWorkerWrapper.Rd @@ -13,7 +13,7 @@ Worker wrapper for macOS sandbox-exec isolation } \keyword{internal} \section{Super class}{ -\code{\link[replr:WorkerWrapper]{replr::WorkerWrapper}} -> \code{MacOSSandboxWorkerWrapper} +\code{\link[repljail:WorkerWrapper]{repljail::WorkerWrapper}} -> \code{MacOSSandboxWorkerWrapper} } \section{Methods}{ \subsection{Public methods}{ @@ -25,9 +25,9 @@ Worker wrapper for macOS sandbox-exec isolation \if{html}{\out{
Inherited methods
}} diff --git a/man/NativeWorkerWrapper.Rd b/man/NativeWorkerWrapper.Rd index 6dca9b8..9a9f935 100644 --- a/man/NativeWorkerWrapper.Rd +++ b/man/NativeWorkerWrapper.Rd @@ -13,7 +13,7 @@ Worker wrapper for native R processes (no sandboxing) } \keyword{internal} \section{Super class}{ -\code{\link[replr:WorkerWrapper]{replr::WorkerWrapper}} -> \code{NativeWorkerWrapper} +\code{\link[repljail:WorkerWrapper]{repljail::WorkerWrapper}} -> \code{NativeWorkerWrapper} } \section{Methods}{ \subsection{Public methods}{ @@ -25,9 +25,9 @@ Worker wrapper for native R processes (no sandboxing) \if{html}{\out{
Inherited methods
}} diff --git a/man/RREPLSession.Rd b/man/RREPLSession.Rd index 4347f98..630141d 100644 --- a/man/RREPLSession.Rd +++ b/man/RREPLSession.Rd @@ -11,7 +11,7 @@ RREPLSession: R6 Class for Isolated R Session Management \details{ An R6 class that provides object-oriented interface for managing isolated R worker processes. This class provides automatic resource management -through finalizers. Docker usage is controlled by the 'replr.use.docker' option. +through finalizers. Worker isolation type is controlled by the 'repljail.worker.type' option. } \section{Constructor}{ diff --git a/man/cleanup_docker_containers.Rd b/man/cleanup_docker_containers.Rd index a488bd4..a5c791f 100644 --- a/man/cleanup_docker_containers.Rd +++ b/man/cleanup_docker_containers.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/utils.R \name{cleanup_docker_containers} \alias{cleanup_docker_containers} -\title{Clean up orphaned replr Docker containers} +\title{Clean up orphaned repljail Docker containers} \usage{ cleanup_docker_containers() } @@ -10,5 +10,5 @@ cleanup_docker_containers() logical, TRUE if cleanup was successful } \description{ -Remove any leftover replr worker containers that may be running +Remove any leftover repljail worker containers that may be running } diff --git a/man/cleanup_docker_networks.Rd b/man/cleanup_docker_networks.Rd index db29431..26a24b4 100644 --- a/man/cleanup_docker_networks.Rd +++ b/man/cleanup_docker_networks.Rd @@ -10,5 +10,5 @@ cleanup_docker_networks() logical, TRUE if cleanup was successful } \description{ -Remove orphaned replr Docker networks that may have been left behind +Remove orphaned repljail Docker networks that may have been left behind } diff --git a/man/dot-replr_sessions.Rd b/man/dot-repljail_sessions.Rd similarity index 76% rename from man/dot-replr_sessions.Rd rename to man/dot-repljail_sessions.Rd index 9cd4624..f9aff61 100644 --- a/man/dot-replr_sessions.Rd +++ b/man/dot-repljail_sessions.Rd @@ -1,14 +1,14 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R \docType{data} -\name{.replr_sessions} -\alias{.replr_sessions} -\title{replr Tools for REPL Session Management} +\name{.repljail_sessions} +\alias{.repljail_sessions} +\title{repljail Tools for REPL Session Management} \format{ An object of class \code{environment} of length 0. } \usage{ -.replr_sessions +.repljail_sessions } \description{ This module provides tools designed specifically for LLM agents (e.g. in ellmer) to @@ -18,14 +18,14 @@ optimized for LLM consumption. } \section{Session Management}{ -The replr tools maintain a global registry of active REPL sessions, +The repljail tools maintain a global registry of active REPL sessions, allowing LLM agents to create multiple concurrent sessions and manage them independently. } \section{Response Format}{ -All replr tools return standardized responses with the following structure: +All repljail tools return standardized responses with the following structure: \itemize{ \item \code{success} - logical, whether the operation succeeded \item \code{message} - character, human-readable status message diff --git a/man/enable_debug.Rd b/man/enable_debug.Rd index 014979f..8b240ce 100644 --- a/man/enable_debug.Rd +++ b/man/enable_debug.Rd @@ -10,5 +10,5 @@ enable_debug(enable = TRUE) \item{enable}{logical, TRUE to enable debug logging, FALSE to disable} } \description{ -Convenience function to enable debug logging for the replr package +Convenience function to enable debug logging for the repljail package } diff --git a/man/get_worker_docker_image.Rd b/man/get_worker_docker_image.Rd index 511908e..395b8f8 100644 --- a/man/get_worker_docker_image.Rd +++ b/man/get_worker_docker_image.Rd @@ -11,5 +11,5 @@ character, Docker image name } \description{ Get the Docker image name to use for worker containers. -Reads from option 'replr.worker.docker.image' if set, otherwise uses default. +Reads from option 'repljail.worker.docker.image' if set, otherwise uses default. } diff --git a/man/is_debug_enabled.Rd b/man/is_debug_enabled.Rd index 334e4c1..66d03b4 100644 --- a/man/is_debug_enabled.Rd +++ b/man/is_debug_enabled.Rd @@ -10,6 +10,6 @@ is_debug_enabled() logical, TRUE if debug logging is enabled } \description{ -Configurable debug logging using the cli package and the replr.debug option +Configurable debug logging using the cli package and the repljail.debug option Check if debug logging is enabled } diff --git a/man/replr.Rd b/man/repljail.Rd similarity index 88% rename from man/replr.Rd rename to man/repljail.Rd index c4a52e2..0126454 100644 --- a/man/replr.Rd +++ b/man/repljail.Rd @@ -1,8 +1,8 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/replr-package.R -\name{replr} -\alias{replr} -\title{replr: Isolated REPL functionality for R} +\name{repljail} +\alias{repljail} +\title{repljail: Isolated REPL functionality for R} \description{ Provides isolated REPL functionality for R, allowing users to execute R code in a separate environment using worker processes for security and stability. diff --git a/man/replr_check_syntax.Rd b/man/repljail_check_syntax.Rd similarity index 78% rename from man/replr_check_syntax.Rd rename to man/repljail_check_syntax.Rd index e9310b0..3bde7f5 100644 --- a/man/replr_check_syntax.Rd +++ b/man/repljail_check_syntax.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_check_syntax} -\alias{replr_check_syntax} +\name{repljail_check_syntax} +\alias{repljail_check_syntax} \title{Check R Code Syntax} \usage{ -replr_check_syntax(code) +repljail_check_syntax(code) } \arguments{ \item{code}{character, R code to check for syntax errors} @@ -20,13 +20,13 @@ making it safe for checking potentially problematic code. \examples{ \dontrun{ # Check valid code -result <- replr_check_syntax("x <- 1 + 2\nprint(x)") +result <- repljail_check_syntax("x <- 1 + 2\nprint(x)") if (result$success) { cat("Valid syntax with", result$data$expression_count, "expressions\n") } # Check invalid code -result <- replr_check_syntax("x <- mean(c(1, 2, 3)") +result <- repljail_check_syntax("x <- mean(c(1, 2, 3)") if (!result$success) { cat("Syntax error:", result$error, "\n") } diff --git a/man/replr_check_syntax_tool.Rd b/man/repljail_check_syntax_tool.Rd similarity index 81% rename from man/replr_check_syntax_tool.Rd rename to man/repljail_check_syntax_tool.Rd index 558e852..08d6e94 100644 --- a/man/replr_check_syntax_tool.Rd +++ b/man/repljail_check_syntax_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_check_syntax_tool} -\alias{replr_check_syntax_tool} +\name{repljail_check_syntax_tool} +\alias{repljail_check_syntax_tool} \title{Check R Code Syntax Tool Definition} \usage{ -replr_check_syntax_tool() +repljail_check_syntax_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to validate R code syntax safely. \examples{ \dontrun{ # Get the tool definition -syntax_tool <- replr_check_syntax_tool() +syntax_tool <- repljail_check_syntax_tool() print(syntax_tool$name) print(syntax_tool$description) } diff --git a/man/replr_cleanup_sessions.Rd b/man/repljail_cleanup_sessions.Rd similarity index 63% rename from man/replr_cleanup_sessions.Rd rename to man/repljail_cleanup_sessions.Rd index d27fd5a..4473bc1 100644 --- a/man/replr_cleanup_sessions.Rd +++ b/man/repljail_cleanup_sessions.Rd @@ -1,22 +1,22 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_cleanup_sessions} -\alias{replr_cleanup_sessions} +\name{repljail_cleanup_sessions} +\alias{repljail_cleanup_sessions} \title{Clean Up Dead REPL Sessions} \usage{ -replr_cleanup_sessions() +repljail_cleanup_sessions() } \value{ list with cleanup results } \description{ -Removes dead sessions from the replr session registry. This is useful +Removes dead sessions from the repljail session registry. This is useful for cleanup when sessions may have died unexpectedly. } \examples{ \dontrun{ # Clean up any dead sessions -result <- replr_cleanup_sessions() +result <- repljail_cleanup_sessions() cat("Cleaned up", result$data$cleaned_count, "dead sessions") } } diff --git a/man/replr_cleanup_sessions_tool.Rd b/man/repljail_cleanup_sessions_tool.Rd similarity index 78% rename from man/replr_cleanup_sessions_tool.Rd rename to man/repljail_cleanup_sessions_tool.Rd index c36fc90..eb79af0 100644 --- a/man/replr_cleanup_sessions_tool.Rd +++ b/man/repljail_cleanup_sessions_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_cleanup_sessions_tool} -\alias{replr_cleanup_sessions_tool} +\name{repljail_cleanup_sessions_tool} +\alias{repljail_cleanup_sessions_tool} \title{Cleanup Sessions Tool Definition} \usage{ -replr_cleanup_sessions_tool() +repljail_cleanup_sessions_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to remove dead sessions from the registry. \examples{ \dontrun{ # Get the tool definition -cleanup_tool <- replr_cleanup_sessions_tool() +cleanup_tool <- repljail_cleanup_sessions_tool() print(cleanup_tool$name) } } diff --git a/man/replr_create_repl_session.Rd b/man/repljail_create_repl_session.Rd similarity index 76% rename from man/replr_create_repl_session.Rd rename to man/repljail_create_repl_session.Rd index 2159aee..d43a4ab 100644 --- a/man/replr_create_repl_session.Rd +++ b/man/repljail_create_repl_session.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_create_repl_session} -\alias{replr_create_repl_session} -\title{Create a New REPL Session for replr} +\name{repljail_create_repl_session} +\alias{repljail_create_repl_session} +\title{Create a New REPL Session for repljail} \usage{ -replr_create_repl_session(timeout = 10) +repljail_create_repl_session(timeout = 10) } \arguments{ \item{timeout}{numeric, timeout in seconds for session startup (default: 10)} @@ -20,7 +20,7 @@ Session names are automatically generated with a friendly format. \examples{ \dontrun{ # Create a new session with auto-generated ID -result <- replr_create_repl_session() +result <- repljail_create_repl_session() if (result$success) { session_id <- result$data$session_id cat("Created session:", session_id) diff --git a/man/replr_create_repl_session_tool.Rd b/man/repljail_create_repl_session_tool.Rd similarity index 77% rename from man/replr_create_repl_session_tool.Rd rename to man/repljail_create_repl_session_tool.Rd index d45b57b..6f69174 100644 --- a/man/replr_create_repl_session_tool.Rd +++ b/man/repljail_create_repl_session_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_create_repl_session_tool} -\alias{replr_create_repl_session_tool} +\name{repljail_create_repl_session_tool} +\alias{repljail_create_repl_session_tool} \title{Create REPL Session Tool Definition} \usage{ -replr_create_repl_session_tool() +repljail_create_repl_session_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to create isolated R sessions. \examples{ \dontrun{ # Get the tool definition -create_tool <- replr_create_repl_session_tool() +create_tool <- repljail_create_repl_session_tool() print(create_tool$name) print(create_tool$description) } diff --git a/man/replr_execute_code.Rd b/man/repljail_execute_code.Rd similarity index 70% rename from man/replr_execute_code.Rd rename to man/repljail_execute_code.Rd index d55c934..f674aa4 100644 --- a/man/replr_execute_code.Rd +++ b/man/repljail_execute_code.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_execute_code} -\alias{replr_execute_code} +\name{repljail_execute_code} +\alias{repljail_execute_code} \title{Execute R Code in a REPL Session} \usage{ -replr_execute_code(session_id, code, timeout = 30) +repljail_execute_code(session_id, code, timeout = 30) } \arguments{ \item{session_id}{character, ID of the session to execute code in} @@ -17,23 +17,23 @@ replr_execute_code(session_id, code, timeout = 30) list with execution results, output, warnings, errors, and success status } \description{ -Executes R code in a specified replr REPL session and returns the results +Executes R code in a specified repljail REPL session and returns the results in a structured format suitable for LLM processing. } \examples{ \dontrun{ # Create a session and execute code -session_result <- replr_create_repl_session() +session_result <- repljail_create_repl_session() session_id <- session_result$data$session_id # Execute simple arithmetic -result <- replr_execute_code(session_id, "2 + 2") +result <- repljail_execute_code(session_id, "2 + 2") if (result$success) { cat("Output:", result$data$output) } # Execute more complex code -result <- replr_execute_code(session_id, " +result <- repljail_execute_code(session_id, " data <- data.frame(x = 1:5, y = letters[1:5]) summary(data) ") diff --git a/man/replr_execute_code_tool.Rd b/man/repljail_execute_code_tool.Rd similarity index 80% rename from man/replr_execute_code_tool.Rd rename to man/repljail_execute_code_tool.Rd index b7ef421..5289154 100644 --- a/man/replr_execute_code_tool.Rd +++ b/man/repljail_execute_code_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_execute_code_tool} -\alias{replr_execute_code_tool} +\name{repljail_execute_code_tool} +\alias{repljail_execute_code_tool} \title{Execute Code Tool Definition} \usage{ -replr_execute_code_tool() +repljail_execute_code_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to execute code in isolated R environments. \examples{ \dontrun{ # Get the tool definition -execute_tool <- replr_execute_code_tool() +execute_tool <- repljail_execute_code_tool() print(execute_tool$name) } } diff --git a/man/replr_get_session_info.Rd b/man/repljail_get_session_info.Rd similarity index 66% rename from man/replr_get_session_info.Rd rename to man/repljail_get_session_info.Rd index 16e7cc6..f6a22d1 100644 --- a/man/replr_get_session_info.Rd +++ b/man/repljail_get_session_info.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_get_session_info} -\alias{replr_get_session_info} +\name{repljail_get_session_info} +\alias{repljail_get_session_info} \title{Get Information About a REPL Session} \usage{ -replr_get_session_info(session_id) +repljail_get_session_info(session_id) } \arguments{ \item{session_id}{character, ID of the session to query} @@ -13,13 +13,13 @@ replr_get_session_info(session_id) list with session information and status } \description{ -Retrieves detailed information about a specific replr REPL session, +Retrieves detailed information about a specific repljail REPL session, including its status, process information, and activity. } \examples{ \dontrun{ # Get information about a session -result <- replr_get_session_info("my_session_id") +result <- repljail_get_session_info("my_session_id") if (result$success) { print(result$data) } diff --git a/man/replr_get_session_info_tool.Rd b/man/repljail_get_session_info_tool.Rd similarity index 78% rename from man/replr_get_session_info_tool.Rd rename to man/repljail_get_session_info_tool.Rd index 195d7dc..66d3c26 100644 --- a/man/replr_get_session_info_tool.Rd +++ b/man/repljail_get_session_info_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_get_session_info_tool} -\alias{replr_get_session_info_tool} +\name{repljail_get_session_info_tool} +\alias{repljail_get_session_info_tool} \title{Get Session Info Tool Definition} \usage{ -replr_get_session_info_tool() +repljail_get_session_info_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to query session status and details. \examples{ \dontrun{ # Get the tool definition -info_tool <- replr_get_session_info_tool() +info_tool <- repljail_get_session_info_tool() print(info_tool$description) } } diff --git a/man/replr_lint_code.Rd b/man/repljail_lint_code.Rd similarity index 84% rename from man/replr_lint_code.Rd rename to man/repljail_lint_code.Rd index 2afb962..f1721cf 100644 --- a/man/replr_lint_code.Rd +++ b/man/repljail_lint_code.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_lint_code} -\alias{replr_lint_code} +\name{repljail_lint_code} +\alias{repljail_lint_code} \title{Lint R Code Without Executing It} \usage{ -replr_lint_code(code, linters = NULL) +repljail_lint_code(code, linters = NULL) } \arguments{ \item{code}{character, R code to lint} @@ -24,13 +24,13 @@ identifying potential issues before execution. \examples{ \dontrun{ # Lint simple code -result <- replr_lint_code("x = 1") +result <- repljail_lint_code("x = 1") if (result$success) { print(result$data$lints) } # Lint code with specific linters -result <- replr_lint_code( +result <- repljail_lint_code( "my_var <- 1", linters = c("object_name_linter", "line_length_linter") ) diff --git a/man/replr_lint_code_tool.Rd b/man/repljail_lint_code_tool.Rd similarity index 82% rename from man/replr_lint_code_tool.Rd rename to man/repljail_lint_code_tool.Rd index 94f4f67..f048ae5 100644 --- a/man/replr_lint_code_tool.Rd +++ b/man/repljail_lint_code_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_lint_code_tool} -\alias{replr_lint_code_tool} +\name{repljail_lint_code_tool} +\alias{repljail_lint_code_tool} \title{Lint Code Tool Definition} \usage{ -replr_lint_code_tool() +repljail_lint_code_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to check code quality using lintr. \examples{ \dontrun{ # Get the tool definition -lint_tool <- replr_lint_code_tool() +lint_tool <- repljail_lint_code_tool() print(lint_tool$name) print(lint_tool$description) } diff --git a/man/replr_list_sessions.Rd b/man/repljail_list_sessions.Rd similarity index 69% rename from man/replr_list_sessions.Rd rename to man/repljail_list_sessions.Rd index 1851b74..a5c16da 100644 --- a/man/replr_list_sessions.Rd +++ b/man/repljail_list_sessions.Rd @@ -1,22 +1,22 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_list_sessions} -\alias{replr_list_sessions} +\name{repljail_list_sessions} +\alias{repljail_list_sessions} \title{List All Active REPL Sessions} \usage{ -replr_list_sessions() +repljail_list_sessions() } \value{ list with information about all active sessions } \description{ -Returns a list of all currently active replr REPL sessions with their +Returns a list of all currently active repljail REPL sessions with their basic information. } \examples{ \dontrun{ # List all active sessions -result <- replr_list_sessions() +result <- repljail_list_sessions() if (result$success) { for (session in result$data$sessions) { cat("Session:", session$session_id, "- Alive:", session$is_alive, "\n") diff --git a/man/replr_list_sessions_tool.Rd b/man/repljail_list_sessions_tool.Rd similarity index 79% rename from man/replr_list_sessions_tool.Rd rename to man/repljail_list_sessions_tool.Rd index 0a5ff2a..eaf0060 100644 --- a/man/replr_list_sessions_tool.Rd +++ b/man/repljail_list_sessions_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_list_sessions_tool} -\alias{replr_list_sessions_tool} +\name{repljail_list_sessions_tool} +\alias{repljail_list_sessions_tool} \title{List Sessions Tool Definition} \usage{ -replr_list_sessions_tool() +repljail_list_sessions_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to enumerate active sessions. \examples{ \dontrun{ # Get the tool definition -list_tool <- replr_list_sessions_tool() +list_tool <- repljail_list_sessions_tool() print(list_tool$name) } } diff --git a/man/replr_run_r_code.Rd b/man/repljail_run_r_code.Rd similarity index 83% rename from man/replr_run_r_code.Rd rename to man/repljail_run_r_code.Rd index 57758c2..8857376 100644 --- a/man/replr_run_r_code.Rd +++ b/man/repljail_run_r_code.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_run_r_code} -\alias{replr_run_r_code} +\name{repljail_run_r_code} +\alias{repljail_run_r_code} \title{Run R Code (Simple Interface)} \usage{ -replr_run_r_code(code, timeout = 30) +repljail_run_r_code(code, timeout = 30) } \arguments{ \item{code}{character, R code to execute} @@ -22,13 +22,13 @@ automatically. Ideal for one-off code execution without manual session managemen \examples{ \dontrun{ # Execute simple arithmetic -result <- replr_run_r_code("2 + 2") +result <- repljail_run_r_code("2 + 2") if (result$success) { cat("Output:", result$data$output) } # Execute more complex code -result <- replr_run_r_code(" +result <- repljail_run_r_code(" data <- data.frame(x = 1:5, y = letters[1:5]) summary(data) ") diff --git a/man/replr_run_r_code_tool.Rd b/man/repljail_run_r_code_tool.Rd similarity index 82% rename from man/replr_run_r_code_tool.Rd rename to man/repljail_run_r_code_tool.Rd index 4bbeea7..cd46c85 100644 --- a/man/replr_run_r_code_tool.Rd +++ b/man/repljail_run_r_code_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_run_r_code_tool} -\alias{replr_run_r_code_tool} +\name{repljail_run_r_code_tool} +\alias{repljail_run_r_code_tool} \title{Run R Code Tool Definition} \usage{ -replr_run_r_code_tool() +repljail_run_r_code_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to execute R code without manual session management. \examples{ \dontrun{ # Get the tool definition -run_tool <- replr_run_r_code_tool() +run_tool <- repljail_run_r_code_tool() print(run_tool$name) print(run_tool$description) } diff --git a/man/replr_stop_all_sessions.Rd b/man/repljail_stop_all_sessions.Rd similarity index 67% rename from man/replr_stop_all_sessions.Rd rename to man/repljail_stop_all_sessions.Rd index 1da560e..c77bff2 100644 --- a/man/replr_stop_all_sessions.Rd +++ b/man/repljail_stop_all_sessions.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_stop_all_sessions} -\alias{replr_stop_all_sessions} +\name{repljail_stop_all_sessions} +\alias{repljail_stop_all_sessions} \title{Stop All REPL Sessions} \usage{ -replr_stop_all_sessions(timeout = 5) +repljail_stop_all_sessions(timeout = 5) } \arguments{ \item{timeout}{numeric, timeout in seconds for each session shutdown (default: 5)} @@ -13,13 +13,13 @@ replr_stop_all_sessions(timeout = 5) list with shutdown results } \description{ -Stops all active replr REPL sessions and clears the session registry. +Stops all active repljail REPL sessions and clears the session registry. Useful for cleanup at the end of an LLM agent session. } \examples{ \dontrun{ # Stop all sessions at the end of analysis -result <- replr_stop_all_sessions() +result <- repljail_stop_all_sessions() cat("Stopped", result$data$stopped_count, "sessions") } } diff --git a/man/replr_stop_all_sessions_tool.Rd b/man/repljail_stop_all_sessions_tool.Rd similarity index 78% rename from man/replr_stop_all_sessions_tool.Rd rename to man/repljail_stop_all_sessions_tool.Rd index 10e2095..0be1b59 100644 --- a/man/replr_stop_all_sessions_tool.Rd +++ b/man/repljail_stop_all_sessions_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_stop_all_sessions_tool} -\alias{replr_stop_all_sessions_tool} +\name{repljail_stop_all_sessions_tool} +\alias{repljail_stop_all_sessions_tool} \title{Stop All Sessions Tool Definition} \usage{ -replr_stop_all_sessions_tool() +repljail_stop_all_sessions_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to perform complete session cleanup. \examples{ \dontrun{ # Get the tool definition -stop_all_tool <- replr_stop_all_sessions_tool() +stop_all_tool <- repljail_stop_all_sessions_tool() print(stop_all_tool$description) } } diff --git a/man/replr_stop_session.Rd b/man/repljail_stop_session.Rd similarity index 67% rename from man/replr_stop_session.Rd rename to man/repljail_stop_session.Rd index a1a7ade..990fe98 100644 --- a/man/replr_stop_session.Rd +++ b/man/repljail_stop_session.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_stop_session} -\alias{replr_stop_session} +\name{repljail_stop_session} +\alias{repljail_stop_session} \title{Stop a REPL Session} \usage{ -replr_stop_session(session_id, timeout = 5) +repljail_stop_session(session_id, timeout = 5) } \arguments{ \item{session_id}{character, ID of the session to stop} @@ -15,13 +15,13 @@ replr_stop_session(session_id, timeout = 5) list with stop operation status } \description{ -Gracefully stops a specified replr REPL session and removes it from +Gracefully stops a specified repljail REPL session and removes it from the session registry. } \examples{ \dontrun{ # Stop a specific session -result <- replr_stop_session("my_session_id") +result <- repljail_stop_session("my_session_id") if (result$success) { cat("Session stopped successfully") } diff --git a/man/replr_stop_session_tool.Rd b/man/repljail_stop_session_tool.Rd similarity index 80% rename from man/replr_stop_session_tool.Rd rename to man/repljail_stop_session_tool.Rd index 17dd67a..406c4b6 100644 --- a/man/replr_stop_session_tool.Rd +++ b/man/repljail_stop_session_tool.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellmer-tools.R -\name{replr_stop_session_tool} -\alias{replr_stop_session_tool} +\name{repljail_stop_session_tool} +\alias{repljail_stop_session_tool} \title{Stop Session Tool Definition} \usage{ -replr_stop_session_tool() +repljail_stop_session_tool() } \value{ An ellmer tool object (when ellmer is available) or a compatible @@ -18,7 +18,7 @@ understand how to gracefully stop and clean up sessions. \examples{ \dontrun{ # Get the tool definition -stop_tool <- replr_stop_session_tool() +stop_tool <- repljail_stop_session_tool() print(stop_tool$description) } } diff --git a/man/start_worker.Rd b/man/start_worker.Rd index 958c188..4b0511c 100644 --- a/man/start_worker.Rd +++ b/man/start_worker.Rd @@ -16,7 +16,7 @@ list with process object and connection info } \description{ Spawn a worker R process using processx that runs the worker script. -Can use different isolation strategies: native, Docker, or firejail. -Isolation method is controlled by options: 'replr.use.firejail' or 'replr.use.docker'. +Can use different isolation strategies: native, Docker, firejail, or macOS sandbox. +Isolation method is controlled by the 'repljail.worker.type' option. } \keyword{internal} diff --git a/tests/testthat.R b/tests/testthat.R index d297f19..1f08eee 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,3 +1,3 @@ library(testthat) -library(replr) -test_check("replr") +library(repljail) +test_check("repljail") diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 922f173..3c40c21 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1,9 +1,9 @@ tryCatch( { - library(replr) + library(repljail) }, error = function(e) { - # Try to load all source files manually if replr package is not installed + # Try to load all source files manually if repljail package is not installed tryCatch( { devtools::load_all() @@ -11,7 +11,7 @@ tryCatch( error = function(e2) { # Skip loading if we can't load source files cat( - "Warning: Could not load replr package or source files:", + "Warning: Could not load repljail package or source files:", e2$message, "\n" ) diff --git a/tests/testthat/test-conversation-logger.R b/tests/testthat/test-conversation-logger.R index d5fce4e..78f15e3 100644 --- a/tests/testthat/test-conversation-logger.R +++ b/tests/testthat/test-conversation-logger.R @@ -157,7 +157,7 @@ test_that("ConversationLogger logs tool requests for R code", { # Simulate a tool request tool_request <- list( - name = "replr_execute_code", + name = "repljail_execute_code", arguments = list( session_id = "test-session", code = "x <- 1 + 1\nprint(x)" @@ -169,21 +169,21 @@ test_that("ConversationLogger logs tool requests for R code", { # Check log contains tool call log <- logger$get_log() expect_true(grepl("### Tool Call", log)) - expect_true(grepl("\\*\\*Tool:\\*\\* `replr_execute_code`", log)) + expect_true(grepl("\\*\\*Tool:\\*\\* `repljail_execute_code`", log)) expect_true(grepl("\\*\\*Code:\\*\\*", log)) expect_true(grepl("```r", log)) expect_true(grepl("x <- 1 \\+ 1", log)) expect_true(grepl("\\*\\*Session:\\*\\* test-session", log)) }) -test_that("ConversationLogger logs tool requests for replr_run_r_code", { +test_that("ConversationLogger logs tool requests for repljail_run_r_code", { logger <- ConversationLogger$new() mock_chat <- create_mock_chat() logger$attach(mock_chat) # Simulate a tool request tool_request <- list( - name = "replr_run_r_code", + name = "repljail_run_r_code", arguments = list( code = "mean(1:10)" ) @@ -194,7 +194,7 @@ test_that("ConversationLogger logs tool requests for replr_run_r_code", { # Check log contains tool call with code block log <- logger$get_log() expect_true(grepl("### Tool Call", log)) - expect_true(grepl("\\*\\*Tool:\\*\\* `replr_run_r_code`", log)) + expect_true(grepl("\\*\\*Tool:\\*\\* `repljail_run_r_code`", log)) expect_true(grepl("```r", log)) expect_true(grepl("mean\\(1:10\\)", log)) }) @@ -223,12 +223,12 @@ test_that("ConversationLogger logs generic tool requests", { expect_true(grepl("```json", log)) }) -test_that("ConversationLogger logs tool results with replr format", { +test_that("ConversationLogger logs tool results with repljail format", { logger <- ConversationLogger$new() mock_chat <- create_mock_chat() logger$attach(mock_chat) - # Simulate a tool result in replr format + # Simulate a tool result in repljail format tool_result <- list( success = TRUE, message = "Code executed successfully", @@ -322,7 +322,7 @@ test_that("ConversationLogger logs generic tool results", { mock_chat <- create_mock_chat() logger$attach(mock_chat) - # Simulate a generic tool result (not replr format) + # Simulate a generic tool result (not repljail format) tool_result <- list( result = "some generic result", status = "ok" diff --git a/tests/testthat/test-debug-integration.R b/tests/testthat/test-debug-integration.R index 5bb6c3b..fd880b0 100644 --- a/tests/testthat/test-debug-integration.R +++ b/tests/testthat/test-debug-integration.R @@ -7,7 +7,7 @@ test_that("Debug logging works end-to-end", { skip_if_not_installed("processx") # Test without debug logging first - options(replr.debug = FALSE) + options(repljail.debug = FALSE) session <- RREPLSession$new(timeout = 10) tryCatch( @@ -28,18 +28,18 @@ test_that("Debug logging works end-to-end", { test_that("Debug logging can be enabled and disabled", { # Test enabling debug enable_debug(TRUE) - expect_true(getOption("replr.debug")) + expect_true(getOption("repljail.debug")) # Test disabling debug enable_debug(FALSE) - expect_false(getOption("replr.debug")) + expect_false(getOption("repljail.debug")) # Test debug status reporting - options(replr.debug = TRUE) + options(repljail.debug = TRUE) status <- debug_status() expect_true(status) - options(replr.debug = FALSE) + options(repljail.debug = FALSE) status <- debug_status() expect_false(status) }) @@ -48,7 +48,7 @@ test_that("Worker inherits debug setting from parent", { skip_on_check() # Enable debug logging - options(replr.debug = TRUE) + options(repljail.debug = TRUE) session <- RREPLSession$new(timeout = 10) @@ -65,7 +65,7 @@ test_that("Worker inherits debug setting from parent", { }, finally = { session$stop(timeout = 5) - options(replr.debug = FALSE) # Clean up + options(repljail.debug = FALSE) # Clean up } ) }) diff --git a/tests/testthat/test-docker.R b/tests/testthat/test-docker.R index f5e03dc..309e4a5 100644 --- a/tests/testthat/test-docker.R +++ b/tests/testthat/test-docker.R @@ -12,7 +12,7 @@ test_that("Docker availability detection works", { skip_on_ci_for_docker() # Should return logical value - result <- replr:::is_docker_available() + result <- repljail:::is_docker_available() expect_type(result, "logical") expect_length(result, 1) }) @@ -20,7 +20,7 @@ test_that("Docker availability detection works", { test_that("Docker image name is defined", { skip_on_ci_for_docker() - image_name <- replr:::get_worker_docker_image() + image_name <- repljail:::get_worker_docker_image() expect_type(image_name, "character") expect_length(image_name, 1) expect_true(nchar(image_name) > 0) @@ -31,12 +31,12 @@ test_that("Docker session can be created and execute commands", { skip_on_ci_for_docker() # Skip if Docker is not available - skip_if_not(replr:::is_docker_available(), "Docker not available") + skip_if_not(repljail:::is_docker_available(), "Docker not available") # Set option to use Docker - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "docker") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "docker") # Create a session (should use Docker due to option) session <- RREPLSession$new(timeout = 30) @@ -63,18 +63,18 @@ test_that("Docker network isolation can be enabled", { skip_on_ci_for_docker() # Skip if Docker is not available - skip_if_not(replr:::is_docker_available(), "Docker not available") + skip_if_not(repljail:::is_docker_available(), "Docker not available") # Set options to use only Docker with network isolation - old_worker_type <- getOption("replr.worker.type") - old_network <- getOption("replr.worker.docker.network.isolation") + old_worker_type <- getOption("repljail.worker.type") + old_network <- getOption("repljail.worker.docker.network.isolation") on.exit({ - options(replr.worker.type = old_worker_type) - options(replr.worker.docker.network.isolation = old_network) + options(repljail.worker.type = old_worker_type) + options(repljail.worker.docker.network.isolation = old_network) }) - options(replr.worker.type = "docker") - options(replr.worker.docker.network.isolation = TRUE) + options(repljail.worker.type = "docker") + options(repljail.worker.docker.network.isolation = TRUE) # Create a session (should use Docker with network isolation) session <- RREPLSession$new(timeout = 30) @@ -100,13 +100,13 @@ test_that("Docker network cleanup works", { skip_on_ci_for_docker() # Skip if Docker is not available - skip_if_not(replr:::is_docker_available(), "Docker not available") + skip_if_not(repljail:::is_docker_available(), "Docker not available") # Create a test network - test_network <- paste0("replr-network-test-", as.integer(Sys.time())) + test_network <- paste0("repljail-network-test-", as.integer(Sys.time())) # Create the network - result <- replr:::create_docker_network(test_network) + result <- repljail:::create_docker_network(test_network) expect_true(result) # Verify network exists @@ -126,7 +126,7 @@ test_that("Docker network cleanup works", { expect_true(test_network %in% networks) # Clean up the network - result <- replr:::remove_docker_network(test_network) + result <- repljail:::remove_docker_network(test_network) expect_true(result) # Verify network is gone @@ -151,18 +151,18 @@ test_that("Docker network is cleaned up when session stops", { skip_on_ci_for_docker() # Skip if Docker is not available - skip_if_not(replr:::is_docker_available(), "Docker not available") + skip_if_not(repljail:::is_docker_available(), "Docker not available") # Set options to use only Docker with network isolation - old_worker_type <- getOption("replr.worker.type") - old_network <- getOption("replr.worker.docker.network.isolation") + old_worker_type <- getOption("repljail.worker.type") + old_network <- getOption("repljail.worker.docker.network.isolation") on.exit({ - options(replr.worker.type = old_worker_type) - options(replr.worker.docker.network.isolation = old_network) + options(repljail.worker.type = old_worker_type) + options(repljail.worker.docker.network.isolation = old_network) }) - options(replr.worker.type = "docker") - options(replr.worker.docker.network.isolation = TRUE) + options(repljail.worker.type = "docker") + options(repljail.worker.docker.network.isolation = TRUE) # Create a session session <- RREPLSession$new(timeout = 30) @@ -215,18 +215,18 @@ test_that("Network isolation provides inter-container isolation", { skip_on_ci_for_docker() # Skip if Docker is not available - skip_if_not(replr:::is_docker_available(), "Docker not available") + skip_if_not(repljail:::is_docker_available(), "Docker not available") # Set options to use only Docker with network isolation - old_worker_type <- getOption("replr.worker.type") - old_network <- getOption("replr.worker.docker.network.isolation") + old_worker_type <- getOption("repljail.worker.type") + old_network <- getOption("repljail.worker.docker.network.isolation") on.exit({ - options(replr.worker.type = old_worker_type) - options(replr.worker.docker.network.isolation = old_network) + options(repljail.worker.type = old_worker_type) + options(repljail.worker.docker.network.isolation = old_network) }) - options(replr.worker.type = "docker") - options(replr.worker.docker.network.isolation = TRUE) + options(repljail.worker.type = "docker") + options(repljail.worker.docker.network.isolation = TRUE) # Create a session with network isolation session <- RREPLSession$new(timeout = 30) @@ -303,12 +303,12 @@ test_that("Network isolation provides inter-container isolation", { test_that("Multiple Docker workers can run simultaneously with different ports", { skip_on_check() skip_on_ci_for_docker() - skip_if_not(replr::is_docker_available(), "Docker not available") + skip_if_not(repljail::is_docker_available(), "Docker not available") # Set worker type to Docker - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "docker") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "docker") session1 <- RREPLSession$new(timeout = 20) session2 <- RREPLSession$new(timeout = 20) @@ -347,15 +347,15 @@ test_that("Multiple Docker workers can run simultaneously with different ports", test_that("Docker worker startup handles port conflicts", { skip_on_check() skip_on_ci_for_docker() - skip_if_not(replr::is_docker_available(), "Docker not available") + skip_if_not(repljail::is_docker_available(), "Docker not available") # Set worker type to Docker - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "docker") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "docker") # Start first session on specific port - port1 <- replr:::get_available_port() + port1 <- repljail:::get_available_port() session1 <- RREPLSession$new(port = port1, timeout = 20) tryCatch( diff --git a/tests/testthat/test-ellmer-tools.R b/tests/testthat/test-ellmer-tools.R index e931d15..d121351 100644 --- a/tests/testthat/test-ellmer-tools.R +++ b/tests/testthat/test-ellmer-tools.R @@ -1,10 +1,10 @@ # Test ellmer tools functionality here::i_am("tests/testthat/test-ellmer-tools.R") -test_that("replr_create_repl_session works", { +test_that("repljail_create_repl_session works", { skip_on_check() # Test creating a session with auto-generated ID - result <- replr::replr_create_repl_session(timeout = 15) + result <- repljail::repljail_create_repl_session(timeout = 15) expect_true(result$success) expect_true(is.character(result$data$session_id)) @@ -14,15 +14,15 @@ test_that("replr_create_repl_session works", { # Clean up session_id <- result$data$session_id - cleanup_result <- replr_stop_session(session_id) + cleanup_result <- repljail_stop_session(session_id) expect_true(cleanup_result$success) }) -test_that("replr_create_repl_session generates unique IDs", { +test_that("repljail_create_repl_session generates unique IDs", { skip_on_check() # Test creating multiple sessions and verify they get unique IDs - result1 <- replr_create_repl_session(timeout = 15) - result2 <- replr_create_repl_session(timeout = 15) + result1 <- repljail_create_repl_session(timeout = 15) + result2 <- repljail_create_repl_session(timeout = 15) expect_true(result1$success) expect_true(result2$success) @@ -33,30 +33,30 @@ test_that("replr_create_repl_session generates unique IDs", { expect_true(result2$data$is_alive) # Clean up - cleanup_result1 <- replr_stop_session(result1$data$session_id) - cleanup_result2 <- replr_stop_session(result2$data$session_id) + cleanup_result1 <- repljail_stop_session(result1$data$session_id) + cleanup_result2 <- repljail_stop_session(result2$data$session_id) expect_true(cleanup_result1$success) expect_true(cleanup_result2$success) }) -test_that("replr_execute_code works", { +test_that("repljail_execute_code works", { skip_on_check() # Create session - session_result <- replr_create_repl_session(timeout = 15) + session_result <- repljail_create_repl_session(timeout = 15) expect_true(session_result$success) session_id <- session_result$data$session_id tryCatch( { # Test simple arithmetic - result <- replr_execute_code(session_id, "2 + 2") + result <- repljail_execute_code(session_id, "2 + 2") expect_true(result$success) expect_equal(result$data$status, "success") expect_true(length(result$data$output) > 0) expect_true(any(grepl("4", result$data$output))) # Test code with warning - warning_result <- replr_execute_code( + warning_result <- repljail_execute_code( session_id, "warning('test warning'); 42" ) @@ -66,41 +66,41 @@ test_that("replr_execute_code works", { expect_true(any(grepl("test warning", warning_result$data$warnings))) # Test code with error - error_result <- replr_execute_code(session_id, "stop('test error')") + error_result <- repljail_execute_code(session_id, "stop('test error')") expect_false(error_result$success) expect_equal(error_result$data$status, "error") expect_true(length(error_result$data$errors) > 0) # Test that session survives error and continues working - recovery_result <- replr_execute_code(session_id, "3 * 4") + recovery_result <- repljail_execute_code(session_id, "3 * 4") expect_true(recovery_result$success) expect_true(any(grepl("12", recovery_result$data$output))) }, finally = { # Clean up - replr_stop_session(session_id) + repljail_stop_session(session_id) } ) }) -test_that("replr_execute_code handles non-existent session", { +test_that("repljail_execute_code handles non-existent session", { skip_on_check() - result <- replr_execute_code("non_existent_session", "1 + 1") + result <- repljail_execute_code("non_existent_session", "1 + 1") expect_false(result$success) expect_equal(result$error, "SESSION_NOT_FOUND") }) -test_that("replr_get_session_info works", { +test_that("repljail_get_session_info works", { skip_on_check() # Create session - session_result <- replr_create_repl_session(timeout = 15) + session_result <- repljail_create_repl_session(timeout = 15) expect_true(session_result$success) session_id <- session_result$data$session_id tryCatch( { # Get session info - info_result <- replr_get_session_info(session_id) + info_result <- repljail_get_session_info(session_id) expect_true(info_result$success) expect_equal(info_result$data$session_id, session_id) expect_true(info_result$data$is_alive) @@ -109,35 +109,35 @@ test_that("replr_get_session_info works", { }, finally = { # Clean up - replr_stop_session(session_id) + repljail_stop_session(session_id) } ) }) -test_that("replr_get_session_info handles non-existent session", { +test_that("repljail_get_session_info handles non-existent session", { skip_on_check() - result <- replr_get_session_info("non_existent_session") + result <- repljail_get_session_info("non_existent_session") expect_false(result$success) expect_equal(result$error, "SESSION_NOT_FOUND") }) -test_that("replr_list_sessions works", { +test_that("repljail_list_sessions works", { skip_on_check() # Test with no sessions - initial_result <- replr_list_sessions() + initial_result <- repljail_list_sessions() expect_true(initial_result$success) expect_equal(initial_result$data$count, 0) # Create a couple of sessions - session1_result <- replr_create_repl_session(timeout = 15) - session2_result <- replr_create_repl_session(timeout = 15) + session1_result <- repljail_create_repl_session(timeout = 15) + session2_result <- repljail_create_repl_session(timeout = 15) expect_true(session1_result$success) expect_true(session2_result$success) tryCatch( { # List sessions - list_result <- replr_list_sessions() + list_result <- repljail_list_sessions() expect_true(list_result$success) expect_equal(list_result$data$count, 2) expect_equal(length(list_result$data$sessions), 2) @@ -149,64 +149,64 @@ test_that("replr_list_sessions works", { }, finally = { # Clean up - replr_stop_session(session1_result$data$session_id) - replr_stop_session(session2_result$data$session_id) + repljail_stop_session(session1_result$data$session_id) + repljail_stop_session(session2_result$data$session_id) } ) }) -test_that("replr_stop_session works", { +test_that("repljail_stop_session works", { skip_on_check() # Create session - session_result <- replr_create_repl_session(timeout = 15) + session_result <- repljail_create_repl_session(timeout = 15) expect_true(session_result$success) session_id <- session_result$data$session_id # Stop session - stop_result <- replr_stop_session(session_id) + stop_result <- repljail_stop_session(session_id) expect_true(stop_result$success) expect_equal(stop_result$data$session_id, session_id) # Verify session is no longer in registry - list_result <- replr_list_sessions() + list_result <- repljail_list_sessions() session_ids <- sapply(list_result$data$sessions, function(s) s$session_id) expect_false(session_id %in% session_ids) }) -test_that("replr_stop_session handles non-existent session", { +test_that("repljail_stop_session handles non-existent session", { skip_on_check() - result <- replr_stop_session("non_existent_session") + result <- repljail_stop_session("non_existent_session") expect_false(result$success) expect_equal(result$error, "SESSION_NOT_FOUND") }) -test_that("replr_stop_all_sessions works", { +test_that("repljail_stop_all_sessions works", { skip_on_check() # Create multiple sessions - session1_result <- replr_create_repl_session(timeout = 15) - session2_result <- replr_create_repl_session(timeout = 15) + session1_result <- repljail_create_repl_session(timeout = 15) + session2_result <- repljail_create_repl_session(timeout = 15) expect_true(session1_result$success) expect_true(session2_result$success) # Verify sessions exist - list_result <- replr_list_sessions() + list_result <- repljail_list_sessions() expect_equal(list_result$data$count, 2) # Stop all sessions - stop_all_result <- replr_stop_all_sessions() + stop_all_result <- repljail_stop_all_sessions() expect_true(stop_all_result$success) expect_equal(stop_all_result$data$stopped_count, 2) # Verify no sessions remain - final_list_result <- replr_list_sessions() + final_list_result <- repljail_list_sessions() expect_equal(final_list_result$data$count, 0) }) -test_that("replr_cleanup_sessions works", { +test_that("repljail_cleanup_sessions works", { skip_on_check() # For this test, we'll just verify the function runs without error # since it's hard to simulate dead sessions in a test - cleanup_result <- replr_cleanup_sessions() + cleanup_result <- repljail_cleanup_sessions() expect_true(cleanup_result$success) expect_true(is.numeric(cleanup_result$data$cleaned_count)) expect_true(is.numeric(cleanup_result$data$remaining_sessions)) @@ -215,19 +215,19 @@ test_that("replr_cleanup_sessions works", { test_that("Multiple sessions maintain isolation", { skip_on_check() # Create two sessions - session1_result <- replr_create_repl_session(timeout = 15) - session2_result <- replr_create_repl_session(timeout = 15) + session1_result <- repljail_create_repl_session(timeout = 15) + session2_result <- repljail_create_repl_session(timeout = 15) expect_true(session1_result$success) expect_true(session2_result$success) tryCatch( { # Set different variables in each session - result1 <- replr_execute_code( + result1 <- repljail_execute_code( session1_result$data$session_id, "my_var <- 'Session 1'" ) - result2 <- replr_execute_code( + result2 <- repljail_execute_code( session2_result$data$session_id, "my_var <- 'Session 2'" ) @@ -235,8 +235,8 @@ test_that("Multiple sessions maintain isolation", { expect_true(result2$success) # Verify isolation - each session should only see its own variable - check1 <- replr_execute_code(session1_result$data$session_id, "my_var") - check2 <- replr_execute_code(session2_result$data$session_id, "my_var") + check1 <- repljail_execute_code(session1_result$data$session_id, "my_var") + check2 <- repljail_execute_code(session2_result$data$session_id, "my_var") expect_true(check1$success) expect_true(check2$success) @@ -245,64 +245,64 @@ test_that("Multiple sessions maintain isolation", { }, finally = { # Clean up - replr_stop_session(session1_result$data$session_id) - replr_stop_session(session2_result$data$session_id) + repljail_stop_session(session1_result$data$session_id) + repljail_stop_session(session2_result$data$session_id) } ) }) -test_that("replr_run_r_code works with simple arithmetic", { +test_that("repljail_run_r_code works with simple arithmetic", { skip_on_check() # Test simple arithmetic - result <- replr_run_r_code("2 + 2") + result <- repljail_run_r_code("2 + 2") expect_true(result$success) expect_equal(result$data$status, "success") expect_true(length(result$data$output) > 0) expect_true(any(grepl("4", result$data$output))) # Verify session was cleaned up (check no new sessions exist) - list_result <- replr_list_sessions() + list_result <- repljail_list_sessions() initial_count <- list_result$data$count # Run another simple calculation - result2 <- replr_run_r_code("3 * 7") + result2 <- repljail_run_r_code("3 * 7") expect_true(result2$success) expect_true(any(grepl("21", result2$data$output))) # Verify still same number of sessions (auto-cleanup worked) - list_result2 <- replr_list_sessions() + list_result2 <- repljail_list_sessions() expect_equal(list_result2$data$count, initial_count) }) -test_that("replr_run_r_code works with complex code", { +test_that("repljail_run_r_code works with complex code", { skip_on_check() code <- " data <- data.frame(x = 1:5, y = letters[1:5]) summary(data) " - result <- replr_run_r_code(code) + result <- repljail_run_r_code(code) expect_true(result$success) expect_equal(result$data$status, "success") expect_true(length(result$data$output) > 0) }) -test_that("replr_run_r_code handles errors correctly", { +test_that("repljail_run_r_code handles errors correctly", { skip_on_check() # Test code with error - result <- replr_run_r_code("stop('test error')") + result <- repljail_run_r_code("stop('test error')") expect_false(result$success) expect_equal(result$data$status, "error") expect_true(length(result$data$errors) > 0) # Verify session was still cleaned up - list_result <- replr_list_sessions() + list_result <- repljail_list_sessions() expect_equal(list_result$data$count, 0) }) -test_that("replr_run_r_code handles warnings", { +test_that("repljail_run_r_code handles warnings", { skip_on_check() # Test code with warning - result <- replr_run_r_code("warning('test warning'); 42") + result <- repljail_run_r_code("warning('test warning'); 42") expect_true(result$success) expect_equal(result$data$status, "success") expect_true(length(result$data$warnings) > 0) @@ -310,52 +310,52 @@ test_that("replr_run_r_code handles warnings", { expect_true(any(grepl("42", result$data$output))) }) -test_that("replr_run_r_code respects timeout parameter", { +test_that("repljail_run_r_code respects timeout parameter", { skip_on_check() # Test with custom timeout - result <- replr_run_r_code("Sys.sleep(0.1); 'done'", timeout = 5) + result <- repljail_run_r_code("Sys.sleep(0.1); 'done'", timeout = 5) expect_true(result$success) expect_true(any(grepl("done", result$data$output))) }) -test_that("replr_check_syntax works with valid code", { +test_that("repljail_check_syntax works with valid code", { # Test simple valid code - result <- replr_check_syntax("x <- 1 + 2") + result <- repljail_check_syntax("x <- 1 + 2") expect_true(result$success) expect_true(result$data$valid) expect_equal(result$data$expression_count, 1) expect_null(result$error) # Test multiple expressions - result2 <- replr_check_syntax("x <- 1\ny <- 2\nz <- x + y") + result2 <- repljail_check_syntax("x <- 1\ny <- 2\nz <- x + y") expect_true(result2$success) expect_true(result2$data$valid) expect_equal(result2$data$expression_count, 3) }) -test_that("replr_check_syntax detects syntax errors", { +test_that("repljail_check_syntax detects syntax errors", { # Test missing closing parenthesis - result <- replr_check_syntax("x <- mean(c(1, 2, 3)") + result <- repljail_check_syntax("x <- mean(c(1, 2, 3)") expect_false(result$success) expect_false(result$data$valid) expect_true(nchar(result$error) > 0) expect_true(grepl("unexpected", result$error)) # Test invalid function syntax - result2 <- replr_check_syntax("x <- function() {\n return 42\n}") + result2 <- repljail_check_syntax("x <- function() {\n return 42\n}") expect_false(result2$success) expect_false(result2$data$valid) expect_true(nchar(result2$error) > 0) }) -test_that("replr_check_syntax handles empty code", { - result <- replr_check_syntax("") +test_that("repljail_check_syntax handles empty code", { + result <- repljail_check_syntax("") expect_true(result$success) expect_true(result$data$valid) expect_equal(result$data$expression_count, 0) }) -test_that("replr_check_syntax handles complex valid code", { +test_that("repljail_check_syntax handles complex valid code", { code <- " library(ggplot2) data <- data.frame(x = 1:10, y = rnorm(10)) @@ -364,30 +364,30 @@ plot <- ggplot(data, aes(x, y)) + theme_minimal() print(plot) " - result <- replr_check_syntax(code) + result <- repljail_check_syntax(code) expect_true(result$success) expect_true(result$data$valid) expect_true(result$data$expression_count > 0) }) -test_that("replr_check_syntax does not execute code", { +test_that("repljail_check_syntax does not execute code", { # Code that would fail at runtime but has valid syntax code <- " x <- nonexistent_function() y <- undefined_variable + 1 " - result <- replr_check_syntax(code) + result <- repljail_check_syntax(code) # Should succeed because syntax is valid even though runtime would fail expect_true(result$success) expect_true(result$data$valid) }) -test_that("replr_lint_code works with clean code", { +test_that("repljail_lint_code works with clean code", { skip_if_not_installed("lintr") # Test code with no issues clean_code <- "x <- 1\ny <- 2\nz <- x + y" - result <- replr_lint_code(clean_code) + result <- repljail_lint_code(clean_code) expect_true(result$success) expect_equal(result$data$lint_count, 0) @@ -395,12 +395,12 @@ test_that("replr_lint_code works with clean code", { expect_true(grepl("No linting issues", result$message)) }) -test_that("replr_lint_code detects style issues", { +test_that("repljail_lint_code detects style issues", { skip_if_not_installed("lintr") # Test code with assignment operator issue (= instead of <-) bad_code <- "x = 1" - result <- replr_lint_code(bad_code) + result <- repljail_lint_code(bad_code) expect_true(result$success) expect_true(result$data$lint_count > 0) @@ -414,22 +414,22 @@ test_that("replr_lint_code detects style issues", { expect_true(is.character(lint$type)) }) -test_that("replr_lint_code works with multiline code", { +test_that("repljail_lint_code works with multiline code", { skip_if_not_installed("lintr") # Test multiline code multiline_code <- "x = 1\ny = 2\nz <- x + y" - result <- replr_lint_code(multiline_code) + result <- repljail_lint_code(multiline_code) expect_true(result$success) expect_true(result$data$lint_count >= 2) # At least 2 assignment issues }) -test_that("replr_lint_code returns proper structure", { +test_that("repljail_lint_code returns proper structure", { skip_if_not_installed("lintr") # Test that result has expected structure - result <- replr_lint_code("x = 1") + result <- repljail_lint_code("x = 1") expect_true(is.list(result)) expect_true("success" %in% names(result)) @@ -444,10 +444,10 @@ test_that("replr_lint_code returns proper structure", { expect_true("lints" %in% names(result$data)) }) -test_that("replr_lint_code handles errors gracefully", { +test_that("repljail_lint_code handles errors gracefully", { skip_if_not_installed("lintr") # This should not error even with unusual input - result <- replr_lint_code("") + result <- repljail_lint_code("") expect_true(result$success) }) diff --git a/tests/testthat/test-end-to-end.R b/tests/testthat/test-end-to-end.R index a2c627f..d4b59c8 100644 --- a/tests/testthat/test-end-to-end.R +++ b/tests/testthat/test-end-to-end.R @@ -5,7 +5,7 @@ test_that("Complete worker lifecycle works", { skip_on_check() # Create session - session <- replr::RREPLSession$new(timeout = 10) + session <- repljail::RREPLSession$new(timeout = 10) # Verify session started successfully expect_true(session$is_alive()) diff --git a/tests/testthat/test-firejail.R b/tests/testthat/test-firejail.R index f1b3802..b06e5a9 100644 --- a/tests/testthat/test-firejail.R +++ b/tests/testthat/test-firejail.R @@ -6,7 +6,7 @@ test_that("Firejail availability detection works", { testthat::skip_on_ci() # Should return logical value - result <- replr:::is_firejail_available() + result <- repljail:::is_firejail_available() expect_type(result, "logical") expect_length(result, 1) }) @@ -16,15 +16,15 @@ test_that("Firejail worker wrapper can be created", { testthat::skip_on_ci() # Skip if Firejail is not available - skip_if_not(replr:::is_firejail_available(), "Firejail not available") + skip_if_not(repljail:::is_firejail_available(), "Firejail not available") # Ensure only native mode (wrappers can be created without starting workers) - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "native") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "native") # Create a firejail wrapper - wrapper <- replr:::FirejailWorkerWrapper$new() + wrapper <- repljail:::FirejailWorkerWrapper$new() expect_s3_class(wrapper, "FirejailWorkerWrapper") expect_s3_class(wrapper, "WorkerWrapper") @@ -38,12 +38,12 @@ test_that("Firejail session can be created and execute commands", { testthat::skip_on_ci() # Skip if Firejail is not available - skip_if_not(replr:::is_firejail_available(), "Firejail not available") + skip_if_not(repljail:::is_firejail_available(), "Firejail not available") # Set options to use only Firejail - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "firejail") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "firejail") # Create a session (should use Firejail due to option) session <- RREPLSession$new(timeout = 30) @@ -73,16 +73,12 @@ test_that("Firejail provides network isolation", { testthat::skip_on_ci() # Skip if Firejail is not available - skip_if_not(replr:::is_firejail_available(), "Firejail not available") + skip_if_not(repljail:::is_firejail_available(), "Firejail not available") # Set options to use only Firejail - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "firejail") - # Set option to use Firejail (legacy) - old_option <- getOption("replr.use.firejail") - on.exit(options(replr.use.firejail = old_option), add = TRUE) - options(replr.use.firejail = TRUE) + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "firejail") # Create a session with firejail session <- RREPLSession$new(timeout = 30) @@ -162,12 +158,12 @@ test_that("Firejail allows writing to temp directory", { testthat::skip_on_ci() # Skip if Firejail is not available - skip_if_not(replr:::is_firejail_available(), "Firejail not available") + skip_if_not(repljail:::is_firejail_available(), "Firejail not available") # Set options to use only Firejail - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "firejail") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "firejail") # Create a session with firejail session <- RREPLSession$new(timeout = 30) @@ -192,7 +188,7 @@ test_that("Firejail custom profile can be used", { testthat::skip_on_ci() # Skip if Firejail is not available - skip_if_not(replr:::is_firejail_available(), "Firejail not available") + skip_if_not(repljail:::is_firejail_available(), "Firejail not available") # Create a temporary profile file profile_file <- tempfile(fileext = ".profile") @@ -214,18 +210,18 @@ test_that("Firejail custom profile can be used", { ) # Set options to use only Firejail with custom profile - old_worker_type <- getOption("replr.worker.type") - old_profile <- getOption("replr.worker.firejail.profile") + old_worker_type <- getOption("repljail.worker.type") + old_profile <- getOption("repljail.worker.firejail.profile") on.exit( { - options(replr.worker.type = old_worker_type) - options(replr.worker.firejail.profile = old_profile) + options(repljail.worker.type = old_worker_type) + options(repljail.worker.firejail.profile = old_profile) }, add = TRUE ) - options(replr.worker.type = "firejail") - options(replr.worker.firejail.profile = profile_file) + options(repljail.worker.type = "firejail") + options(repljail.worker.firejail.profile = profile_file) # Create a session (should use custom profile) session <- RREPLSession$new(timeout = 30) @@ -244,24 +240,24 @@ test_that("Worker wrapper factory creates correct type", { testthat::skip_on_ci() # Save option - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) # Test native wrapper (default) - options(replr.worker.type = "native") - wrapper <- replr:::create_worker_wrapper() + options(repljail.worker.type = "native") + wrapper <- repljail:::create_worker_wrapper() expect_equal(wrapper$get_metadata()$type, "native") # Test firejail wrapper - skip_if_not(replr:::is_firejail_available(), "Firejail not available") - options(replr.worker.type = "firejail") - wrapper <- replr:::create_worker_wrapper() + skip_if_not(repljail:::is_firejail_available(), "Firejail not available") + options(repljail.worker.type = "firejail") + wrapper <- repljail:::create_worker_wrapper() expect_equal(wrapper$get_metadata()$type, "firejail") # Test invalid type - options(replr.worker.type = "invalid") + options(repljail.worker.type = "invalid") expect_error( - replr:::create_worker_wrapper(), + repljail:::create_worker_wrapper(), "Invalid worker type" ) }) diff --git a/tests/testthat/test-ipc.R b/tests/testthat/test-ipc.R index dfe2f7e..0575204 100644 --- a/tests/testthat/test-ipc.R +++ b/tests/testthat/test-ipc.R @@ -2,7 +2,7 @@ here::i_am("tests/testthat/test-ipc.R") test_that("IPC socket path generation works", { - socket_path <- replr:::get_ipc_socket_path() + socket_path <- repljail:::get_ipc_socket_path() # Check that path is a character string expect_type(socket_path, "character") @@ -10,8 +10,8 @@ test_that("IPC socket path generation works", { # Check that path starts with temp directory expect_true(grepl(tempdir(), socket_path, fixed = TRUE)) - # Check that path contains replr_socket pattern - expect_true(grepl("replr_socket_", socket_path)) + # Check that path contains repljail_socket pattern + expect_true(grepl("repljail_socket_", socket_path)) }) test_that("Native worker uses IPC sockets", { @@ -19,7 +19,7 @@ test_that("Native worker uses IPC sockets", { testthat::skip_on_ci() # Set worker type to native explicitly - options(replr.worker.type = "native") + options(repljail.worker.type = "native") # Create a session session <- RREPLSession$new(timeout = 10) @@ -59,10 +59,10 @@ test_that("Native worker uses IPC sockets", { test_that("Firejail worker uses IPC sockets when available", { skip_on_check() testthat::skip_on_ci() - skip_if_not(replr::is_firejail_available(), "Firejail not available") + skip_if_not(repljail::is_firejail_available(), "Firejail not available") # Set worker type to firejail explicitly - options(replr.worker.type = "firejail") + options(repljail.worker.type = "firejail") # Create a session session <- RREPLSession$new(timeout = 15) @@ -102,10 +102,10 @@ test_that("Firejail worker uses IPC sockets when available", { test_that("Docker worker still uses TCP (not IPC)", { skip_on_check() testthat::skip_on_ci() - skip_if_not(replr::is_docker_available(), "Docker not available") + skip_if_not(repljail::is_docker_available(), "Docker not available") # Set worker type to docker explicitly - options(replr.worker.type = "docker") + options(repljail.worker.type = "docker") # Create a session session <- RREPLSession$new(timeout = 20) @@ -140,7 +140,7 @@ test_that("Worker script accepts socket path argument", { library(processx) # Get worker script path - worker_path <- replr:::get_worker_script_path() + worker_path <- repljail:::get_worker_script_path() # Create a temporary socket path socket_path <- tempfile(pattern = "test_socket_", tmpdir = tempdir()) @@ -188,7 +188,7 @@ test_that("IPC communication works end-to-end", { skip_on_check() testthat::skip_on_ci() # Set worker type to native for IPC - options(replr.worker.type = "native") + options(repljail.worker.type = "native") # Create session session <- RREPLSession$new(timeout = 10) diff --git a/tests/testthat/test-macos-sandbox.R b/tests/testthat/test-macos-sandbox.R index 6e25321..f511fe9 100644 --- a/tests/testthat/test-macos-sandbox.R +++ b/tests/testthat/test-macos-sandbox.R @@ -12,7 +12,7 @@ test_that("macOS sandbox availability detection works", { skip_on_ci_for_macos_sandbox() # Should return logical value - result <- replr:::is_macos_sandbox_available() + result <- repljail:::is_macos_sandbox_available() expect_type(result, "logical") expect_length(result, 1) @@ -28,17 +28,17 @@ test_that("macOS sandbox worker wrapper can be created", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) # Ensure only native mode (wrappers can be created without starting workers) - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "native") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "native") # Create a macOS sandbox wrapper - wrapper <- replr:::MacOSSandboxWorkerWrapper$new() + wrapper <- repljail:::MacOSSandboxWorkerWrapper$new() expect_s3_class(wrapper, "MacOSSandboxWorkerWrapper") expect_s3_class(wrapper, "WorkerWrapper") @@ -53,14 +53,14 @@ test_that("macOS sandbox session can be created and execute commands", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) # Set options to use only macOS sandbox - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "macos-sandbox") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "macos-sandbox") # Create a session (should use macOS sandbox due to option) session <- RREPLSession$new(timeout = 30) @@ -91,14 +91,14 @@ test_that("macOS sandbox provides network isolation", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) # Set options to use only macOS sandbox - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "macos-sandbox") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "macos-sandbox") # Create a session with macOS sandbox session <- RREPLSession$new(timeout = 30) @@ -183,14 +183,14 @@ test_that("macOS sandbox allows writing to temp directory", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) # Set options to use only macOS sandbox - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "macos-sandbox") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "macos-sandbox") # Create a session with macOS sandbox session <- RREPLSession$new(timeout = 30) @@ -216,14 +216,14 @@ test_that("macOS sandbox allows temp directory access", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) # Set options to use only macOS sandbox - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "macos-sandbox") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "macos-sandbox") # Create a session with macOS sandbox session <- RREPLSession$new(timeout = 30) @@ -254,7 +254,7 @@ test_that("macOS sandbox custom profile can be used", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) @@ -275,18 +275,18 @@ test_that("macOS sandbox custom profile can be used", { ) # Set options to use macOS sandbox with custom profile - old_worker_type <- getOption("replr.worker.type") - old_profile <- getOption("replr.worker.macos.sandbox.profile") + old_worker_type <- getOption("repljail.worker.type") + old_profile <- getOption("repljail.worker.macos.sandbox.profile") on.exit( { - options(replr.worker.type = old_worker_type) - options(replr.worker.macos.sandbox.profile = old_profile) + options(repljail.worker.type = old_worker_type) + options(repljail.worker.macos.sandbox.profile = old_profile) }, add = TRUE ) - options(replr.worker.type = "macos-sandbox") - options(replr.worker.macos.sandbox.profile = profile_file) + options(repljail.worker.type = "macos-sandbox") + options(repljail.worker.macos.sandbox.profile = profile_file) # Create a session (should use custom profile) session <- RREPLSession$new(timeout = 30) @@ -306,14 +306,14 @@ test_that("macOS sandbox supports plot generation", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) # Set options to use only macOS sandbox - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "macos-sandbox") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "macos-sandbox") # Create a session with macOS sandbox session <- RREPLSession$new(timeout = 30) @@ -338,39 +338,39 @@ test_that("Worker wrapper factory creates correct type with macOS sandbox", { skip_on_ci_for_macos_sandbox() # Save option - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) # Test native wrapper (default) - options(replr.worker.type = "native") - wrapper <- replr:::create_worker_wrapper() + options(repljail.worker.type = "native") + wrapper <- repljail:::create_worker_wrapper() expect_equal(wrapper$get_metadata()$type, "native") # Test macOS sandbox wrapper skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) - options(replr.worker.type = "macos-sandbox") - wrapper <- replr:::create_worker_wrapper() + options(repljail.worker.type = "macos-sandbox") + wrapper <- repljail:::create_worker_wrapper() expect_equal(wrapper$get_metadata()$type, "macos_sandbox") # Test docker wrapper - options(replr.worker.type = "docker") - wrapper <- replr:::create_worker_wrapper() + options(repljail.worker.type = "docker") + wrapper <- repljail:::create_worker_wrapper() expect_equal(wrapper$get_metadata()$type, "docker") # Test firejail wrapper (if available) - if (replr:::is_firejail_available()) { - options(replr.worker.type = "firejail") - wrapper <- replr:::create_worker_wrapper() + if (repljail:::is_firejail_available()) { + options(repljail.worker.type = "firejail") + wrapper <- repljail:::create_worker_wrapper() expect_equal(wrapper$get_metadata()$type, "firejail") } # Test invalid type - options(replr.worker.type = "invalid") + options(repljail.worker.type = "invalid") expect_error( - replr:::create_worker_wrapper(), + repljail:::create_worker_wrapper(), "Invalid worker type" ) }) @@ -381,14 +381,14 @@ test_that("macOS sandbox temporary profile cleanup works", { # Skip if macOS sandbox is not available skip_if_not( - replr:::is_macos_sandbox_available(), + repljail:::is_macos_sandbox_available(), "macOS sandbox not available" ) # Set options to use only macOS sandbox - old_worker_type <- getOption("replr.worker.type") - on.exit(options(replr.worker.type = old_worker_type)) - options(replr.worker.type = "macos-sandbox") + old_worker_type <- getOption("repljail.worker.type") + on.exit(options(repljail.worker.type = old_worker_type)) + options(repljail.worker.type = "macos-sandbox") # Create a session session <- RREPLSession$new(timeout = 30) diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index a5c4319..48a018c 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -1,11 +1,11 @@ # Tests for Phase 4: Enhanced Plot Handling with Vision Integration here::i_am("tests/testthat/test-plots.R") -test_that("replr_execute_code handles multiple plots correctly", { +test_that("repljail_execute_code handles multiple plots correctly", { skip_on_check() # Create session - create_result <- replr_create_repl_session() + create_result <- repljail_create_repl_session() expect_true(create_result$success) session_id <- create_result$data$session_id @@ -13,7 +13,7 @@ test_that("replr_execute_code handles multiple plots correctly", { tryCatch( { # Execute code that generates multiple plots - result <- replr_execute_code( + result <- repljail_execute_code( session_id, " set.seed(456) @@ -40,16 +40,16 @@ test_that("replr_execute_code handles multiple plots correctly", { }, finally = { # Clean up - replr_stop_session(session_id) + repljail_stop_session(session_id) } ) }) -test_that("replr_execute_code works without plots (backward compatibility)", { +test_that("repljail_execute_code works without plots (backward compatibility)", { skip_on_check() # Create session - create_result <- replr_create_repl_session() + create_result <- repljail_create_repl_session() expect_true(create_result$success) session_id <- create_result$data$session_id @@ -57,7 +57,7 @@ test_that("replr_execute_code works without plots (backward compatibility)", { tryCatch( { # Execute code without plots - result <- replr_execute_code(session_id, "2 + 2", timeout = 10) + result <- repljail_execute_code(session_id, "2 + 2", timeout = 10) expect_true(result$success) expect_equal(result$data$status, "success") @@ -68,7 +68,7 @@ test_that("replr_execute_code works without plots (backward compatibility)", { }, finally = { # Clean up - replr_stop_session(session_id) + repljail_stop_session(session_id) } ) }) diff --git a/tests/testthat/test-worker.R b/tests/testthat/test-worker.R index a2ac170..7f83c41 100644 --- a/tests/testthat/test-worker.R +++ b/tests/testthat/test-worker.R @@ -3,7 +3,7 @@ here::i_am("tests/testthat/test-worker.R") test_that("Worker script exists and is executable", { - worker_path <- replr:::get_worker_script_path() + worker_path <- repljail:::get_worker_script_path() expect_false(worker_path == "") expect_true(file.exists(worker_path)) expect_true(file.access(worker_path, mode = 1) == 0) @@ -11,7 +11,7 @@ test_that("Worker script exists and is executable", { test_that("Worker script validates command line arguments", { # Test with no arguments - worker_path <- replr:::get_worker_script_path() + worker_path <- repljail:::get_worker_script_path() result1 <- suppressWarnings(system2( "Rscript", c(worker_path), @@ -49,7 +49,7 @@ test_that("Worker can be started via processx with IPC socket", { library(processx) # Test that processx can start the worker script with IPC socket - worker_path <- replr:::get_worker_script_path() + worker_path <- repljail:::get_worker_script_path() socket_path <- tempfile(pattern = "test_worker_socket_") on.exit(unlink(socket_path), add = TRUE) @@ -78,7 +78,7 @@ test_that("Worker accepts debug flag with IPC socket", { skip_on_check() library(processx) - worker_path <- replr:::get_worker_script_path() + worker_path <- repljail:::get_worker_script_path() socket_path <- tempfile(pattern = "test_worker_socket_debug_") on.exit(unlink(socket_path), add = TRUE) diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index b389573..2a39b25 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -1,8 +1,8 @@ --- -title: "Getting Started with replr" +title: "Getting Started with repljail" output: rmarkdown::html_vignette vignette: > - %\VignetteIndexEntry{Getting Started with replr} + %\VignetteIndexEntry{Getting Started with repljail} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- @@ -16,14 +16,14 @@ knitr::opts_chunk$set( ## Introduction -`replr` provides isolated REPL (Read-Eval-Print Loop) functionality for R, allowing you to execute R code in separate worker processes. This vignette demonstrates the core features and usage patterns. +`repljail` provides isolated REPL (Read-Eval-Print Loop) functionality for R, allowing you to execute R code in separate worker processes. This vignette demonstrates the core features and usage patterns. ## R6 Class Interface -The primary interface for `replr` is the `RREPLSession` R6 class: +The primary interface for `repljail` is the `RREPLSession` R6 class: ```{r basic-usage, eval = FALSE} -library(replr) +library(repljail) # Create a new isolated R session session <- RREPLSession$new() @@ -46,7 +46,7 @@ session$stop() Workers are resilient and continue processing after errors: ```{r error-handling, eval = FALSE} -library(replr) +library(repljail) session <- RREPLSession$new() @@ -82,7 +82,7 @@ session$stop() Enable debug logging to see detailed information about worker processes: ```{r debug-logging, eval = FALSE} -library(replr) +library(repljail) # Enable debug logging enable_debug(TRUE) @@ -110,7 +110,7 @@ debug_status() You can run multiple isolated sessions simultaneously: ```{r multiple-sessions, eval = FALSE} -library(replr) +library(repljail) # Start multiple isolated sessions session1 <- RREPLSession$new() @@ -141,7 +141,7 @@ session2$stop() Control execution timeouts for long-running operations: ```{r timeouts, eval = FALSE} -library(replr) +library(repljail) session <- RREPLSession$new() @@ -160,6 +160,6 @@ session$stop() ## Next Steps -- Learn about [Docker container support](https://github.com/pkrusche/replr#docker-container-support) for enhanced security -- Explore [ellmer integration](https://github.com/pkrusche/replr#ellmer-tools-for-llm-agents) for LLM agent capabilities +- Learn about [Docker container support](https://github.com/pkrusche/repljail#docker-container-support) for enhanced security +- Explore [ellmer integration](https://github.com/pkrusche/repljail#ellmer-tools-for-llm-agents) for LLM agent capabilities - Check out example scripts in `inst/examples/` diff --git a/vignettes/network-isolation.Rmd b/vignettes/network-isolation.Rmd index 0f46397..1b0b0aa 100644 --- a/vignettes/network-isolation.Rmd +++ b/vignettes/network-isolation.Rmd @@ -16,7 +16,7 @@ knitr::opts_chunk$set( ## Overview -The `replr` package implements **true air-gapped execution** for R worker processes using Docker's `--internal` network flag combined with a gateway sidecar pattern. This provides complete internet isolation while maintaining host-to-worker communication. +The `repljail` package implements **true air-gapped execution** for R worker processes using Docker's `--internal` network flag combined with a gateway sidecar pattern. This provides complete internet isolation while maintaining host-to-worker communication. ## Architecture @@ -46,7 +46,7 @@ The `replr` package implements **true air-gapped execution** for R worker proces │ NO EXTERNAL/INTERNET ACCESS ▼ ┌─────────────────────────────────────────────────────────────────┐ -│ Worker Container (replr-worker) │ +│ Worker Container (repljail-worker) │ │ │ │ R Process │ │ └─ nanonext REP socket │ @@ -67,7 +67,7 @@ docker network create \ --driver bridge \ --internal \ # Blocks ALL external access --subnet 172.28.0.0/16 \ # Custom subnet to avoid conflicts - replr-network-- + repljail-network-- ``` **Key change**: Removed `--opt com.docker.network.bridge.enable_icc=false` which was blocking gateway→worker communication. @@ -76,16 +76,16 @@ docker network create \ ```bash docker run \ - --name replr-worker-- \ - --network replr-network- \ # Isolated network only, no port publish - --user replr \ # Non-root user + --name repljail-worker-- \ + --network repljail-network- \ # Isolated network only, no port publish + --user repljail \ # Non-root user --memory 512m \ --cpus 1.0 \ --read-only \ # Read-only filesystem --tmpfs /tmp:noexec,nosuid \ --security-opt no-new-privileges \ --cap-drop ALL \ - replr-worker:latest \ + repljail-worker:latest \ Rscript /app/worker.R --listen-all --debug ``` @@ -94,13 +94,13 @@ docker run \ ```bash # Step 1: Start gateway with port publish for host access docker run -d \ - --name replr-gateway-- \ + --name repljail-gateway-- \ -p 127.0.0.1::8080 \ alpine/socat \ - TCP-LISTEN:8080,fork,reuseaddr TCP:replr-worker-: + TCP-LISTEN:8080,fork,reuseaddr TCP:repljail-worker-: # Step 2: Connect gateway to internal network for worker access -docker network connect replr-network- replr-gateway- +docker network connect repljail-network- repljail-gateway- ``` ## Security Properties @@ -120,8 +120,8 @@ docker network connect replr-network- replr-gateway- ```{r network-isolation-usage, eval = FALSE} # Enable Docker with network isolation -options(replr.use.docker = TRUE) -options(replr.worker.docker.network.isolation = TRUE) +options(repljail.worker.type = "docker") +options(repljail.worker.docker.network.isolation = TRUE) # Create isolated session session <- RREPLSession$new() @@ -141,17 +141,17 @@ session$stop() ```{r network-isolation-config, eval = FALSE} # Enable Docker workers -options(replr.use.docker = TRUE) +options(repljail.worker.type = "docker") # Enable network isolation (requires Docker) -options(replr.worker.docker.network.isolation = TRUE) +options(repljail.worker.docker.network.isolation = TRUE) # Optional: Customize Docker image -options(replr.worker.docker.image = "replr-worker:latest") +options(repljail.worker.docker.image = "repljail-worker:latest") # Optional: Resource limits -options(replr.worker.docker.memory = "512m") -options(replr.worker.docker.cpus = "1.0") +options(repljail.worker.docker.memory = "512m") +options(repljail.worker.docker.cpus = "1.0") ``` ## Cleanup @@ -161,9 +161,9 @@ All resources are automatically cleaned up when the session stops: ```{r network-isolation-cleanup, eval = FALSE} session$stop() # Automatically removes: -# 1. Worker container (replr-worker-*) -# 2. Gateway container (replr-gateway-*) -# 3. Internal network (replr-network-*) +# 1. Worker container (repljail-worker-*) +# 2. Gateway container (repljail-gateway-*) +# 3. Internal network (repljail-network-*) ``` Manual cleanup of orphaned resources: @@ -192,11 +192,11 @@ enable_debug(TRUE) Check Docker resources: ```bash # List worker containers -docker ps -a --filter "name=replr-worker-" +docker ps -a --filter "name=repljail-worker-" # List gateway containers -docker ps -a --filter "name=replr-gateway-" +docker ps -a --filter "name=repljail-gateway-" # List networks -docker network ls --filter "name=replr-network-" +docker network ls --filter "name=repljail-network-" ```