Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
22 changes: 12 additions & 10 deletions apps/web/src/app/(application)/files/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { useCallback, useRef, useState } from "react"
import React, { useCallback, useRef, useState } from "react"
import { toast } from "sonner"

// UI Components
Expand Down Expand Up @@ -354,16 +354,18 @@ const FilesPage = () => {
<Breadcrumb>
<BreadcrumbList>
{breadcrumbs.map((entry, i) => (
<BreadcrumbItem key={entry.path}>
<React.Fragment key={entry.path}>
{i > 0 && <BreadcrumbSeparator />}
{i === breadcrumbs.length - 1 ? (
<BreadcrumbPage>{entry.name}</BreadcrumbPage>
) : (
<BreadcrumbLink className="cursor-pointer" onClick={() => navigateToBreadcrumb(entry)}>
{entry.name}
</BreadcrumbLink>
)}
</BreadcrumbItem>
<BreadcrumbItem>
{i === breadcrumbs.length - 1 ? (
<BreadcrumbPage>{entry.name}</BreadcrumbPage>
) : (
<BreadcrumbLink className="cursor-pointer" onClick={() => navigateToBreadcrumb(entry)}>
{entry.name}
</BreadcrumbLink>
)}
</BreadcrumbItem>
</React.Fragment>
))}
</BreadcrumbList>
</Breadcrumb>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export async function GET(
// Proxy to gateway
try {
const gatewayUrl = `${GATEWAY_URL}/api/v1/sessions/${conversationid}/artifacts/${encodeURIComponent(filename)}${queryString}`
const response = await fetch(gatewayUrl)
const response = await fetch(gatewayUrl, {
headers: { cookie: reqheaders.get("cookie") ?? "" },
})

if (!response.ok) {
return NextResponse.json({ error: "Artifact not found" }, { status: response.status })
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/utils/file-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export const isPreviewable = (mediatype: string): boolean => {
mediatype === "text/plain" ||
mediatype === "text/markdown" ||
mediatype === "text/csv" ||
mediatype === "application/json"
mediatype === "application/json" ||
mediatype === "application/pdf"
)
}

Expand Down
90 changes: 70 additions & 20 deletions infra/openshell/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,28 @@
# =============================================================================

# ---------------------------------------------------------------------------
# Stage 1: Build sandbox-server and produce a deployable bundle
# Stage 1: Build sandbox-server and produce a deployable bundle.
# Also pre-builds all native npm globals here so the runtime stage does not
# need a C/C++ toolchain.
# ---------------------------------------------------------------------------
FROM node:22-slim AS builder

# Build-time system dependencies:
# - build-essential / g++ / make / pkg-config: required by node-gyp for native modules
# - *-dev packages: header files needed to compile canvas (cairo, gif, jpeg, pango, rsvg)
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
g++ \
libcairo2-dev \
libgif-dev \
libjpeg-dev \
libpango1.0-dev \
librsvg2-dev \
make \
pkg-config \
python3 \
&& rm -rf /var/lib/apt/lists/*

RUN corepack enable && corepack prepare [email protected] --activate

WORKDIR /build
Expand Down Expand Up @@ -53,35 +71,66 @@ RUN pnpm --filter @openzosma/sandbox-server deploy /deploy --prod --legacy
# Copy built dist into the deploy directory
RUN cp -r packages/sandbox-server/dist /deploy/dist

# Pre-build all global npm packages that contain native modules or are large.
# Installing here (with the toolchain present) means the runtime stage only
# needs to COPY the pre-compiled /usr/local/lib/node_modules directory —
# no compiler required at runtime.
RUN npm install -g \
@mariozechner/pi-coding-agent \
agent-slack \
chart.js \
chartjs-node-canvas \
d3 \
exceljs

# ---------------------------------------------------------------------------
# Stage 2: Runtime
# ---------------------------------------------------------------------------
FROM node:22-slim

# Install runtime dependencies and developer tools.
# iproute2 is required by the OpenShell sandbox supervisor for network namespace creation.
# python3-venv provides virtualenv support for isolated Python environments.
# tree, jq, wget, less, unzip, zip are general-purpose CLI tools the agent can use.
# make + g++ are required for native npm modules that use node-gyp.
# Node.js and npm are already present from the node:22-slim base image.
# Runtime system dependencies only — no compiler toolchain, no *-dev headers.
#
# Shared libraries required by pre-compiled native modules (e.g. canvas):
# libcairo2, libgif7, libjpeg62-turbo, libpango-1.0-0, libpangocairo-1.0-0,
# librsvg2-2 — these are the runtime counterparts of the *-dev packages used
# in the builder stage.
#
# iproute2: required by the OpenShell sandbox supervisor for network namespace creation.
# python3 / python3-pip / python3-venv: agent-generated Python code + report generation.
# tini: PID 1 / signal handling.
# git, curl, wget, jq, less, tree, unzip, zip: general-purpose CLI tools for the agent.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
iproute2 \
jq \
less \
libcairo2 \
libgif7 \
libjpeg62-turbo \
libpango-1.0-0 \
libpangocairo-1.0-0 \
librsvg2-2 \
python3 \
python3-pip \
python3-venv \
tini \
curl \
iproute2 \
tree \
jq \
wget \
less \
unzip \
wget \
zip \
make \
g++ \
&& rm -rf /var/lib/apt/lists/*

# Install Python reporting libraries
RUN pip3 install --break-system-packages \
matplotlib \
numpy \
openpyxl \
pandas \
python-pptx \
reportlab \
seaborn

Comment on lines 102 to +133
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes the docker image heavy?

We might be losing benefits we get from using

FROM node:22-slim

Would it help to move to another image?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will review and do the change if needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Trex-Hub changes made, please review once if legit

# Create sandbox user (OpenShell convention: non-root execution)
# --home is required so homedir() returns a real path; bootstrapPiExtensions()
# writes config files to ~/.pi/ and will crash if home is /nonexistent.
Expand All @@ -95,11 +144,12 @@ WORKDIR /app
# Copy the self-contained deploy bundle (node_modules + dist, no symlinks)
COPY --from=builder /deploy ./

RUN npm install -g @mariozechner/pi-coding-agent

# Install agent-slack CLI so the agent can query Slack from inside the sandbox.
# The CLI reads SLACK_TOKEN from the environment (injected via .env by the orchestrator).
RUN npm install -g agent-slack
# Copy pre-compiled global npm packages from the builder stage.
# All native modules (e.g. canvas inside chartjs-node-canvas) were compiled
# there with the full toolchain. The runtime stage needs only the shared libs
# (installed above), not the compiler.
COPY --from=builder /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=builder /usr/local/bin /usr/local/bin

# Copy extension manifest and install script before switching to sandbox user
COPY packages/agents/extensions.json /tmp/extensions.json
Expand All @@ -123,7 +173,7 @@ RUN chown root:root -R ./skills/ && chmod 444 -R ./skills/
# In per-user persistent sandboxes, this directory persists across sessions.
# /sandbox is the OpenShell writable root where .env is uploaded.
# .knowledge-base is pre-created so the agent prompt doesn't error on ls.
RUN mkdir -p /workspace/.knowledge-base /tmp/agent /sandbox && \
RUN mkdir -p /workspace/.knowledge-base /workspace/output /tmp/agent /sandbox && \
chown -R sandbox:sandbox /workspace /tmp/agent /sandbox

# Copy entrypoint into /app/ which is already in the policy read_only allowlist.
Expand Down
1 change: 1 addition & 0 deletions packages/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"uuid": "^13.0.0",
"@openzosma/db": "workspace:*",
"@openzosma/integrations": "workspace:*",
"@openzosma/skill-reports": "workspace:*",
"pg": "^8.13.1",
"@mariozechner/pi-agent-core": "^0.61.0"
},
Expand Down
15 changes: 11 additions & 4 deletions packages/agents/src/pi.agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import { createLogger } from "@openzosma/logger"
import { bootstrapMemory } from "@openzosma/memory"
import { DEFAULT_SYSTEM_PROMPT } from "./pi/config.js"
import { resolveModel } from "./pi/model.js"
import { createDefaultTools, createListDatabaseSchemasTool, createQueryDatabaseTool } from "./pi/tools.js"
import {
createDefaultTools,
createListDatabaseSchemasTool,
createQueryDatabaseTool,
createReportTools,
} from "./pi/tools.js"
import type { AgentMessage, AgentProvider, AgentSession, AgentSessionOpts, AgentStreamEvent } from "./types.js"

const log = createLogger({ component: "agents" })
Expand Down Expand Up @@ -52,9 +57,11 @@ class PiAgentSession implements AgentSession {
memoryDir: opts.memoryDir,
})
const toolList = [...createDefaultTools(opts.workspaceDir, opts.toolsEnabled)]
const customTools = opts.dbPool
? [createQueryDatabaseTool(opts.dbPool), createListDatabaseSchemasTool(opts.dbPool)]
: undefined
const reportTools = createReportTools(opts.toolsEnabled, opts.workspaceDir)
const customTools = [
...reportTools,
...(opts.dbPool ? [createQueryDatabaseTool(opts.dbPool), createListDatabaseSchemasTool(opts.dbPool)] : []),
]
const { model, apiKey } = resolveModel({
provider: opts.provider,
model: opts.model,
Expand Down
72 changes: 63 additions & 9 deletions packages/agents/src/pi/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,75 @@ import type { ToolDefinition } from "@mariozechner/pi-coding-agent"
import { integrationQueries } from "@openzosma/db"
import type { IntegrationConfig } from "@openzosma/db"
import { executequery, getschema, safeDecrypt } from "@openzosma/integrations"
import {
createReportExecuteCodeTool,
createReportGenerateTool,
createReportListTemplatesTool,
} from "@openzosma/skill-reports"
import { Type } from "@sinclair/typebox"
import type pg from "pg"

export type BuiltInToolName = "read" | "bash" | "edit" | "write" | "grep" | "find" | "ls"
export type BuiltInToolName =
| "read"
| "bash"
| "edit"
| "write"
| "grep"
| "find"
| "ls"
| "report_list_templates"
| "report_generate"
| "report_execute_code"

// Built-in AgentTool entries (accepted by createAgentSession's `tools` field).
const BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"] as const
type BuiltinToolName = (typeof BUILTIN_TOOL_NAMES)[number]

// Custom ToolDefinition entries (accepted by createAgentSession's `customTools` field).
const CUSTOM_TOOL_NAMES = ["report_list_templates", "report_generate", "report_execute_code"] as const
type CustomToolName = (typeof CUSTOM_TOOL_NAMES)[number]

/**
* Create the built-in AgentTool instances (read, bash, edit, write, grep, find, ls).
* These go in `tools` when calling createAgentSession.
*
* @param workspaceDir - Root workspace directory.
* @param toolsEnabled - Optional allow-list of tool names. If omitted, all tools are returned.
*/
export const createDefaultTools = (workspaceDir: string, toolsEnabled?: string[]) => {
const allTools = [
{ name: "read", tool: createReadTool(workspaceDir) },
{ name: "bash", tool: createBashTool(workspaceDir) },
{ name: "edit", tool: createEditTool(workspaceDir) },
{ name: "write", tool: createWriteTool(workspaceDir) },
{ name: "grep", tool: createGrepTool(workspaceDir) },
{ name: "find", tool: createFindTool(workspaceDir) },
{ name: "ls", tool: createLsTool(workspaceDir) },
] as const
{ name: "read" as BuiltinToolName, tool: createReadTool(workspaceDir) },
{ name: "bash" as BuiltinToolName, tool: createBashTool(workspaceDir) },
{ name: "edit" as BuiltinToolName, tool: createEditTool(workspaceDir) },
{ name: "write" as BuiltinToolName, tool: createWriteTool(workspaceDir) },
{ name: "grep" as BuiltinToolName, tool: createGrepTool(workspaceDir) },
{ name: "find" as BuiltinToolName, tool: createFindTool(workspaceDir) },
{ name: "ls" as BuiltinToolName, tool: createLsTool(workspaceDir) },
]

if (!toolsEnabled || toolsEnabled.length === 0) {
return allTools.map((t) => t.tool)
}

const allow = new Set(toolsEnabled)
return allTools.filter((t) => allow.has(t.name)).map((t) => t.tool)
}

/**
* Create the report ToolDefinition instances (report_list_templates, report_generate, report_execute_code).
* These go in `customTools` when calling createAgentSession.
*
* @param toolsEnabled - Optional allow-list of tool names. If omitted, all report tools are returned.
* @param workspaceDir - Workspace root; report output will go to <workspaceDir>/output.
* Defaults to /workspace/output when omitted (sandbox mode).
*/
export const createReportTools = (toolsEnabled?: string[], workspaceDir?: string): ToolDefinition[] => {
const outputDir = workspaceDir ? `${workspaceDir}/output` : undefined
const allTools = [
{ name: "report_list_templates" as CustomToolName, tool: createReportListTemplatesTool({ outputDir }) },
{ name: "report_generate" as CustomToolName, tool: createReportGenerateTool({ outputDir }) },
{ name: "report_execute_code" as CustomToolName, tool: createReportExecuteCodeTool({ outputDir }) },
]

if (!toolsEnabled || toolsEnabled.length === 0) {
return allTools.map((t) => t.tool)
Expand Down
Loading
Loading