diff --git a/Dockerfile b/Dockerfile index fcd7893..b8915af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,19 +3,21 @@ RUN corepack enable && corepack prepare pnpm@latest --activate WORKDIR /app FROM base AS deps +# Copy only dependency manifests first for better layer caching COPY package.json ./ -COPY . . +COPY pnpm-lock.yaml* ./ # better-sqlite3 requires native compilation tools RUN apt-get update && apt-get install -y python3 make g++ --no-install-recommends && rm -rf /var/lib/apt/lists/* RUN if [ -f pnpm-lock.yaml ]; then \ pnpm install --frozen-lockfile; \ else \ - echo "WARN: pnpm-lock.yaml not found in build context; running non-frozen install"; \ + echo "WARN: pnpm-lock.yaml not found in build context; running non-frozen install" && \ pnpm install --no-frozen-lockfile; \ fi FROM base AS build -COPY --from=deps /app ./ +COPY --from=deps /app/node_modules ./node_modules +COPY . . RUN pnpm build FROM node:20-slim AS runtime @@ -24,8 +26,8 @@ ENV NODE_ENV=production RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs COPY --from=build /app/.next/standalone ./ COPY --from=build /app/.next/static ./.next/static -# Copy public directory if it exists (may not exist in all setups) -COPY --from=build /app/public* ./public/ +# Copy schema.sql needed by migration 001_init at runtime +COPY --from=build /app/src/lib/schema.sql ./src/lib/schema.sql # Create data directory with correct ownership for SQLite RUN mkdir -p .data && chown nextjs:nodejs .data RUN apt-get update && apt-get install -y curl --no-install-recommends && rm -rf /var/lib/apt/lists/* diff --git a/docker-compose.yml b/docker-compose.yml index 3c8b968..9a16156 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,12 @@ services: mission-control: build: . + container_name: mission-control ports: - - "3000:3000" - env_file: .env + - "${MC_PORT:-3000}:3000" + env_file: + - path: .env + required: false volumes: - mc-data:/app/.data restart: unless-stopped diff --git a/next.config.js b/next.config.js index e24a943..3ee2c78 100644 --- a/next.config.js +++ b/next.config.js @@ -2,6 +2,8 @@ const nextConfig = { output: 'standalone', turbopack: {}, + // Transpile ESM-only packages so they resolve correctly in all environments + transpilePackages: ['react-markdown', 'remark-gfm'], // Security headers async headers() { diff --git a/src/app/[[...panel]]/page.tsx b/src/app/[[...panel]]/page.tsx index ecfd9f0..e6358eb 100644 --- a/src/app/[[...panel]]/page.tsx +++ b/src/app/[[...panel]]/page.tsx @@ -140,6 +140,9 @@ export default function Home() { return (
+ + Skip to main content + {/* Left: Icon rail navigation (hidden on mobile, shown as bottom bar instead) */} @@ -149,7 +152,7 @@ export default function Home() { -
+
diff --git a/src/components/panels/agent-detail-tabs.tsx b/src/components/panels/agent-detail-tabs.tsx index d67ee19..02b2950 100644 --- a/src/components/panels/agent-detail-tabs.tsx +++ b/src/components/panels/agent-detail-tabs.tsx @@ -1244,6 +1244,8 @@ export function ConfigTab({ const [jsonInput, setJsonInput] = useState('') const [availableModels, setAvailableModels] = useState([]) const [newFallbackModel, setNewFallbackModel] = useState('') + const [newAllowTool, setNewAllowTool] = useState('') + const [newDenyTool, setNewDenyTool] = useState('') useEffect(() => { setConfig(agent.config || {}) @@ -1292,6 +1294,40 @@ export function ConfigTab({ setNewFallbackModel('') } + const updateIdentityField = (field: string, value: string) => { + setConfig((prev: any) => ({ + ...prev, + identity: { ...(prev.identity || {}), [field]: value }, + })) + } + + const updateSandboxField = (field: string, value: string) => { + setConfig((prev: any) => ({ + ...prev, + sandbox: { ...(prev.sandbox || {}), [field]: value }, + })) + } + + const addTool = (list: 'allow' | 'deny', value: string) => { + const trimmed = value.trim() + if (!trimmed) return + setConfig((prev: any) => { + const tools = prev.tools || {} + const existing = Array.isArray(tools[list]) ? tools[list] : [] + if (existing.includes(trimmed)) return prev + return { ...prev, tools: { ...tools, [list]: [...existing, trimmed] } } + }) + } + + const removeTool = (list: 'allow' | 'deny', index: number) => { + setConfig((prev: any) => { + const tools = prev.tools || {} + const existing = Array.isArray(tools[list]) ? [...tools[list]] : [] + existing.splice(index, 1) + return { ...prev, tools: { ...tools, [list]: existing } } + }) + } + const handleSave = async (writeToGateway: boolean = false) => { setSaving(true) setError(null) @@ -1479,60 +1515,205 @@ export function ConfigTab({ {/* Identity */}
Identity
-
- {identityEmoji} -
-
{identityName}
-
{identityTheme}
+ {editing ? ( +
+
+
+ + updateIdentityField('emoji', e.target.value)} + className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm text-center focus:outline-none focus:ring-1 focus:ring-primary/50" + placeholder="🤖" + /> +
+
+ + updateIdentityField('name', e.target.value)} + className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/50" + placeholder="Agent name" + /> +
+
+ + updateIdentityField('theme', e.target.value)} + className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary/50" + placeholder="e.g. backend engineer" + /> +
+
+
+ +