Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func setupTestAgent(t *testing.T) string {
os.MkdirAll(filepath.Join(dir, "agents"), 0o755)
os.WriteFile(filepath.Join(dir, "agents", "default.yaml"), []byte(`name: test-agent
image: quay.io/test:latest
entrypoint: claude --bare
entrypoint: claude
providers:
- profile: github
- profile: vertex-local
Expand Down
2 changes: 0 additions & 2 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ func (c *AgentConfig) BuildRunSh() string {
b.WriteString("PAYLOAD_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\n")
b.WriteString("# Prepend payload bin to PATH\n")
b.WriteString("export PATH=\"$PAYLOAD_DIR/bin:$PATH\"\n\n")
b.WriteString("# Git auth\n")
b.WriteString("gh auth setup-git 2>/dev/null || true\n\n")
b.WriteString("# Validate entrypoint\n")
entrypoint := c.EffectiveEntrypoint()
epBin := strings.Fields(entrypoint)[0]
Expand Down
13 changes: 5 additions & 8 deletions internal/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ providers:
JIRA_URL: https://issues.redhat.com
- profile: github
task: tasks/daily-standup.md
entrypoint: claude --bare
entrypoint: claude
`)
cfg, err := Parse(data)
if err != nil {
Expand Down Expand Up @@ -179,7 +179,7 @@ func TestParseFile_AgentYAML(t *testing.T) {
dir := t.TempDir()
yamlContent := `name: test-agent
image: ghcr.io/test:latest
entrypoint: claude --bare
entrypoint: claude
tty: true
providers:
- profile: github
Expand Down Expand Up @@ -268,7 +268,7 @@ func TestBuildEnvMap_Empty(t *testing.T) {

func TestBuildRunSh(t *testing.T) {
cfg := &AgentConfig{
Entrypoint: "claude --bare",
Entrypoint: "claude",
Task: "tasks/standup.md",
}
runSh := cfg.BuildRunSh()
Expand All @@ -278,13 +278,10 @@ func TestBuildRunSh(t *testing.T) {
if strings.Contains(runSh, "env.sh") {
t.Error("run.sh should not source env.sh — env vars are injected via --env")
}
if !strings.Contains(runSh, "gh auth setup-git") {
t.Error("missing gh auth setup-git")
}
if !strings.Contains(runSh, `command -v "claude"`) {
t.Error("missing entrypoint validation")
}
if !strings.Contains(runSh, `exec claude --bare -p "$(cat "$PAYLOAD_DIR/task.md")"`) {
if !strings.Contains(runSh, `exec claude -p "$(cat "$PAYLOAD_DIR/task.md")"`) {
t.Errorf("missing task exec with -p in:\n%s", runSh)
}
}
Expand All @@ -310,7 +307,7 @@ func TestRenderPayload(t *testing.T) {
{Profile: "atlassian", Env: map[string]string{"JIRA_URL": "https://jira.example.com"}},
},
Task: "my-task.md",
Entrypoint: "claude --bare",
Entrypoint: "claude",
}

destDir := filepath.Join(t.TempDir(), "payload")
Expand Down
48 changes: 12 additions & 36 deletions sandbox/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,17 @@
# Sandbox Agent Instructions
# Sandbox Environment

You are running inside an OpenShell sandbox. Credentials are injected via the OpenShell provider system — they appear as environment variables automatically.
You are running inside an OpenShell sandbox. Credentials are injected via the OpenShell provider system and appear as environment variables automatically.

## Tools Available
## Environment

### GitHub — `gh` CLI
- Pre-authenticated. Use `gh` for all GitHub operations.
- Examples: `gh repo clone`, `gh pr create`, `gh issue list`, `gh api`

### Jira & Confluence — mcp-atlassian MCP server
- Connected via the `atlassian` MCP server (credentials injected by provider).
- Use MCP tools for Jira searches, issue creation, comments, and Confluence page reads.

### Google Workspace — `gws` CLI
- Pre-authenticated for Gmail, Calendar, Drive, Docs, Sheets.
- Path: `/usr/local/bin/gws`
- Examples:
- `gws gmail users messages list --params '{"userId": "me", "maxResults": 5}'`
- `gws calendar events list --params '{"calendarId": "primary", "maxResults": 5}'`
- `gws drive files list --params '{"pageSize": 10}'`
- Use `gws schema <service.resource.method>` to discover API parameters.

### AI Coding Agents
- `claude` — Claude Code (wrapper at `/usr/local/bin/claude`)
- `opencode` — OpenCode (MCP config at `/sandbox/opencode.json`)

### General Tools
- `python3`, `pip`, `uv` — Python with a virtualenv at `/sandbox/.venv`
- `node`, `npm` — Node.js 22
- `git`, `curl` — pre-installed

## Configuration
- Inference routes through the gateway proxy at `inference.local`.
- Model and provider configuration are set by the harness during sandbox creation.

## Conventions
- Working directory: `/sandbox`
- Writable paths: `/sandbox`, `/tmp`
- Network: Outbound allowed to Google APIs, GitHub, Atlassian, npm/pypi.
- Credentials are managed by the OpenShell provider system and cleaned up on sandbox exit.
- Inference routes through the gateway proxy at `inference.local`
- Credentials are managed by OpenShell and cleaned up on sandbox exit

## Tools

- `gh` — GitHub CLI (pre-authenticated). Run `gh auth setup-git` before any git clone/push/pull to configure git credential helper.
- `gws` — Google Workspace CLI (when available). Use `gws schema <service.resource.method>` to discover API parameters.
- MCP servers (Jira, Confluence) are configured in `.mcp.json` and connected automatically.
- `python3`, `uv`, `node`, `npm`, `git`, `curl`
19 changes: 2 additions & 17 deletions sandbox/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@
# GWS config could be provider-managed and the CLI could move to a
# community sandbox.
#
# startup.sh: Baked into the image to wire runtime credentials (Atlassian
# MCP config, GWS file placement). See sandbox.sh for the specific
# workarounds and their upstream tracking issues.

ARG BASE_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest
FROM ${BASE_IMAGE}

Expand All @@ -66,27 +62,16 @@ RUN npm install -g opencode-ai@latest
COPY policy.yaml /etc/openshell/policy.yaml

# Agent instructions + config (Claude Code + OpenCode)
COPY CLAUDE.md /sandbox/CLAUDE.md
COPY CLAUDE.md /sandbox/.claude/CLAUDE.md
COPY settings.json /sandbox/.claude/settings.json
COPY claude.json /sandbox/.claude.json
COPY mcp.json /sandbox/.mcp.json
COPY opencode.json /sandbox/opencode.json

# Runtime script
COPY startup.sh /sandbox/startup.sh

# Claude wrapper: move the real binary aside, install wrapper in its place.
# The base image puts claude at /usr/local/bin/claude; we move it to
# /usr/local/lib/claude-real so the wrapper can call it.
RUN mv /usr/local/bin/claude /usr/local/lib/claude-real
COPY bin/claude /usr/local/bin/claude

# Harness config directory (populated at runtime via upload)
RUN mkdir -p /sandbox/.config/openshell && chown sandbox:sandbox /sandbox/.config/openshell

RUN chmod 755 /usr/local/bin/claude && \
chmod +x /sandbox/startup.sh && \
chown -R sandbox:sandbox /sandbox/.claude /sandbox/.claude.json /sandbox/.mcp.json /sandbox/opencode.json /sandbox/.config /sandbox/CLAUDE.md /sandbox/startup.sh
RUN chown -R sandbox:sandbox /sandbox/.claude /sandbox/.claude.json /sandbox/.mcp.json /sandbox/opencode.json /sandbox/.config

USER sandbox

Expand Down
3 changes: 0 additions & 3 deletions sandbox/bin/claude

This file was deleted.

15 changes: 0 additions & 15 deletions sandbox/startup.sh

This file was deleted.

2 changes: 1 addition & 1 deletion test/test-flow.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ sandbox_verify() {
step "sandbox: gws token placeholder" "$CLI" sandbox exec --name "$name" -- bash -c 'echo "$GOOGLE_WORKSPACE_CLI_TOKEN" | grep -q "openshell:resolve:env"'
step "sandbox: gws api call" "$CLI" sandbox exec --name "$name" -- bash -c 'for i in 1 2 3; do curl -sf https://gmail.googleapis.com/gmail/v1/users/me/profile -H "Authorization: Bearer $GOOGLE_WORKSPACE_CLI_TOKEN" -o /dev/null && exit 0; sleep 3; done; exit 1'
step "sandbox: mcp config" "$CLI" sandbox exec --name "$name" -- test -f /sandbox/.mcp.json
step "sandbox: claude responds" "$CLI" sandbox exec --name "$name" -- bash -c 'echo "respond with ok" | claude --bare --print 2>&1 | head -1'
step "sandbox: claude responds" "$CLI" sandbox exec --name "$name" -- bash -c 'echo "respond with ok" | claude --print 2>&1 | head -1'
}

summary() {
Expand Down
Loading