diff --git a/cmd/helpers_test.go b/cmd/helpers_test.go index 7d8b6d3..f98c417 100644 --- a/cmd/helpers_test.go +++ b/cmd/helpers_test.go @@ -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 diff --git a/internal/agent/agent.go b/internal/agent/agent.go index a62dcc0..c446829 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -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] diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index ae7635a..0c068db 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -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 { @@ -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 @@ -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() @@ -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) } } @@ -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") diff --git a/sandbox/CLAUDE.md b/sandbox/CLAUDE.md index f24ebed..1755813 100644 --- a/sandbox/CLAUDE.md +++ b/sandbox/CLAUDE.md @@ -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 ` 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 ` to discover API parameters. +- MCP servers (Jira, Confluence) are configured in `.mcp.json` and connected automatically. +- `python3`, `uv`, `node`, `npm`, `git`, `curl` diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile index 1c30a28..8404274 100644 --- a/sandbox/Dockerfile +++ b/sandbox/Dockerfile @@ -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} @@ -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 diff --git a/sandbox/bin/claude b/sandbox/bin/claude deleted file mode 100755 index 3605a94..0000000 --- a/sandbox/bin/claude +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -# Wrapper that sets sandbox defaults so Claude Code starts clean. -exec /usr/local/lib/claude-real --bare --dangerously-skip-permissions "$@" diff --git a/sandbox/startup.sh b/sandbox/startup.sh deleted file mode 100644 index fcc7f68..0000000 --- a/sandbox/startup.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -# Runtime setup for the sandbox. Runs once at sandbox creation. -set -euo pipefail - -# ── Source env vars from agent config ───────────────────────────────── -OPENSHELL_DIR="/sandbox/.config/openshell" -if [[ -f "$OPENSHELL_DIR/sandbox.env" ]]; then - . "$OPENSHELL_DIR/sandbox.env" - cat "$OPENSHELL_DIR/sandbox.env" >> /sandbox/.bashrc -fi - -# ── Git auth ────────────────────────────────────────────────────────── -gh auth setup-git 2>/dev/null || true - -echo "Setup complete." diff --git a/test/test-flow.sh b/test/test-flow.sh index c6de262..93a0618 100755 --- a/test/test-flow.sh +++ b/test/test-flow.sh @@ -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() {