Skip to content

Commit 5682bfb

Browse files
authored
CLI Sandbox Integration (#354)
1 parent a0d61f1 commit 5682bfb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+3882
-1920
lines changed

.cursor/rules/general.mdc

Lines changed: 159 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ const { data } = useQuery({
452452
- Sensitive data (passwords, tokens, API keys)
453453
- Real-time data (notifications, live counts)
454454
- Temporary search/filter results
455-
Configured globally in `src/app/providers.tsx` with `PersistQueryClientProvider`.
455+
Configured globally in `src/app/providers.tsx` with `PersistQueryClientProvider`.
456456

457457
**staleTime vs gcTime:**
458458

@@ -785,6 +785,164 @@ export const usedFunction = () => {}; // Keep
785785
// Ignore "unlisted dependencies" warnings
786786
```
787787

788+
## Background Jobs & BullMQ Worker Pattern - MANDATORY
789+
790+
**Use factory functions for workers to avoid module-level side effects.**
791+
792+
### The Problem: Accidental Worker Initialization
793+
794+
When workers are created at module level, importing the module immediately starts workers:
795+
796+
```typescript
797+
// ❌ BAD - Worker starts on import (side effect)
798+
export const buildWorker = createWorker(...);
799+
const events = createQueueEvents(...);
800+
events.on('completed', ...);
801+
console.log('Worker initialized'); // Runs immediately!
802+
```
803+
804+
**Consequence:** Importing this module in API routes accidentally starts workers in the Next.js container.
805+
806+
### The Solution: Factory Functions
807+
808+
Use factory functions that only initialize workers when explicitly called:
809+
810+
```typescript
811+
// ✅ GOOD - No side effects, safe to import anywhere
812+
export function createBuildWorker() {
813+
const worker = createWorker(...);
814+
const events = createQueueEvents(...);
815+
events.on('completed', ...);
816+
console.log('Worker initialized'); // Only runs when called
817+
return worker;
818+
}
819+
```
820+
821+
### Implementation Pattern
822+
823+
**Worker Module** (`src/lib/queue/workers/build.ts`):
824+
825+
```typescript
826+
/**
827+
* Build Worker - Factory function (no side effects)
828+
*/
829+
830+
async function processBuildJob(job: { data: BuildJobData }): Promise<BuildJobResult> {
831+
// Job processing logic
832+
}
833+
834+
/**
835+
* Create and initialize build worker
836+
* Factory function - NO side effects until called
837+
*/
838+
export function createBuildWorker() {
839+
const worker = createWorker<BuildJobData>(QUEUE_NAMES.BUILD, processBuildJob, {
840+
concurrency: 1,
841+
});
842+
843+
const events = createQueueEvents(QUEUE_NAMES.BUILD);
844+
845+
events.on('completed', ({ jobId, returnvalue }) => {
846+
console.log(`[WORKER] ✅ Job ${jobId} completed`);
847+
});
848+
849+
events.on('failed', ({ jobId, failedReason }) => {
850+
console.error(`[WORKER] ❌ Job ${jobId} failed:`, failedReason);
851+
});
852+
853+
console.log('[WORKER] 🚀 Build Worker Initialized');
854+
855+
return worker;
856+
}
857+
```
858+
859+
**Worker Process** (`src/worker.ts`):
860+
861+
```typescript
862+
import { createBuildWorker } from '@/lib/queue/workers/build';
863+
import { createPreviewWorker } from '@/lib/queue/workers/previews';
864+
865+
async function main() {
866+
console.log('[WORKER] 🚀 Starting BullMQ worker process...\n');
867+
868+
// Initialize workers (explicit - no side effects on import)
869+
const previewWorker = createPreviewWorker();
870+
const buildWorker = createBuildWorker();
871+
872+
// Graceful shutdown handlers
873+
process.on('SIGTERM', async () => {
874+
await gracefulShutdown([previewWorker, buildWorker], [previewQueue, buildQueue]);
875+
process.exit(0);
876+
});
877+
}
878+
879+
main();
880+
```
881+
882+
**Barrel Export** (`src/lib/queue/index.ts`):
883+
884+
```typescript
885+
/**
886+
* BullMQ Queue Module
887+
* Safe to import anywhere - worker factories have NO side effects
888+
*/
889+
890+
// Queues (for enqueueing jobs in API routes)
891+
export { buildQueue, type BuildJobData, type BuildJobResult } from './queues/build';
892+
export { previewQueue, schedulePreviewCleanup } from './queues/previews';
893+
894+
// Worker factories (for worker process only, but safe to import anywhere)
895+
export { createBuildWorker } from './workers/build';
896+
export { createPreviewWorker } from './workers/previews';
897+
898+
// Helpers
899+
export async function enqueueBuild(data: BuildJobData): Promise<void> {
900+
const { buildQueue } = await import('./queues/build');
901+
await buildQueue.add('process-build', data);
902+
}
903+
```
904+
905+
### Usage Patterns
906+
907+
**In API Routes** (enqueue jobs):
908+
909+
```typescript
910+
import { buildQueue, enqueueBuild } from '@/lib/queue';
911+
912+
// Enqueue a build job
913+
await enqueueBuild({
914+
buildJobId: 'abc123',
915+
projectId: 'proj-1',
916+
sessionId: 'sess-1',
917+
ticketsPath: 'tickets/2024-01-01.json',
918+
});
919+
```
920+
921+
**In Worker Process** (process jobs):
922+
923+
```typescript
924+
import { createBuildWorker, createPreviewWorker } from '@/lib/queue';
925+
926+
// Explicit initialization - only in worker process
927+
const buildWorker = createBuildWorker();
928+
const previewWorker = createPreviewWorker();
929+
```
930+
931+
### Benefits
932+
933+
✅ **No accidental worker initialization** - Workers only run when explicitly called
934+
✅ **Safe barrel exports** - Can export factory functions without side effects
935+
✅ **Clear initialization** - Worker lifecycle is visible and controllable
936+
✅ **Better testing** - Can test worker logic without starting actual workers
937+
✅ **Explicit control** - Worker lifecycle is visible in `src/worker.ts`
938+
939+
### Rules
940+
941+
- ❌ **NEVER** create workers at module level with `export const worker = createWorker(...)`
942+
- ✅ **ALWAYS** use factory functions: `export function createWorker() { return createWorker(...); }`
943+
- ✅ **ALWAYS** call worker factories explicitly in `src/worker.ts`
944+
- ✅ **NEVER** call worker factories in API routes (only import queues)
945+
788946
## TypeScript and Type Safety Guidelines
789947

790948
- Never use the `any` type - it defeats TypeScript's type checking
@@ -816,7 +974,6 @@ export const usedFunction = () => {}; // Keep
816974
2. **Domain Extension Types** - Only define in `lib/types/` when extending base types AND actively used
817975
3. **Infrastructure Types** - API utilities, errors, and configurations in `lib/api/`
818976

819-
820977
### Type Naming Conventions
821978

822979
- Base types: `User`, `UserSubscription`, `Task` (match schema exports)
@@ -826,7 +983,6 @@ export const usedFunction = () => {}; // Keep
826983
- Input types: Infer from router input `RouterInput['tasks']['create']`
827984
- Statistics: `UserStats`, `BillingStats` (computed aggregations)
828985

829-
830986
## Centralized Type Organization Rules - MANDATORY
831987

832988
**NEVER define types inside hooks, components, or utility functions. ALL types must be centralized.**
@@ -872,7 +1028,6 @@ export interface AsyncOperationOptions {
8721028
onSuccess?: () => void;
8731029
onError?: (error: Error) => void;
8741030
}
875-
8761031
```
8771032

8781033
### ❌ WRONG - Manual type definitions
@@ -953,7 +1108,6 @@ import { tasks } from '@/lib/db/schema'; // OK in database queries
9531108

9541109
**Sitemap** only includes legal pages in `app/sitemap.ts`.
9551110

956-
9571111
## GitHub Actions Workflow Notifications - MANDATORY
9581112

9591113
**When creating new GitHub Actions workflows, ALWAYS add Slack notifications for success and failure outcomes.**

.env.local

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/projects
1414
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
1515
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/projects
1616

17-
# AI Models
18-
# ------------------------------------------------------------------------------------
19-
NEXT_PUBLIC_DEFAULT_MODEL=claude-haiku-4-5
2017

2118
# Ghost
2219
# ------------------------------------------------------------------------------------
@@ -69,6 +66,9 @@ CLERK_SECRET_KEY=replace-me
6966
# AI Provider
7067
# ------------------------------------------------------------------------------------
7168
ANTHROPIC_API_KEY=replace-me
69+
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
70+
GOOGLE_API_KEY=replace-me
71+
GOOGLE_MODEL=gemini-2.0-flash
7272
AGENT_MAX_TURNS=25
7373

7474
# Docker Related
@@ -85,7 +85,7 @@ PREVIEW_RESEND_API_KEY=replace-me
8585

8686
# Sandbox
8787
# ------------------------------------------------------------------------------------
88-
SANDBOX_IMAGE=ghcr.io/kosuke-org/kosuke-sandbox:latest
88+
SANDBOX_IMAGE=kosuke-sandbox-local:latest
8989
SANDBOX_NETWORK=kosuke_network
9090
TRAEFIK_ENABLED=false
9191
SANDBOX_PORT_RANGE_START=4000
@@ -94,6 +94,9 @@ SANDBOX_MEMORY_LIMIT=4294967296 # 4GB
9494
SANDBOX_CPU_SHARES=512
9595
SANDBOX_PIDS_LIMIT=4096
9696
SANDBOX_AGENT_PORT=9000
97+
SANDBOX_BUN_PORT=3000
98+
SANDBOX_PYTHON_PORT=8000
99+
SANDBOX_PLAN_TEST=false
97100

98101
# GitHub App Authentication
99102
# ------------------------------------------------------------------------------------

.env.prod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ CLERK_SECRET_KEY=${CLERK_SECRET_KEY}
1414
# AI Provider
1515
# ------------------------------------------------------------------------------------
1616
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
17+
ANTHROPIC_MODEL=claude-sonnet-4-5-20250929
18+
GOOGLE_API_KEY=${GOOGLE_API_KEY}
19+
GOOGLE_MODEL=gemini-2.0-flash
1720
AGENT_MAX_TURNS=25
1821

1922
# Docker Related
@@ -39,6 +42,9 @@ SANDBOX_MEMORY_LIMIT=4294967296 # 4GB
3942
SANDBOX_CPU_SHARES=512
4043
SANDBOX_PIDS_LIMIT=4096
4144
SANDBOX_AGENT_PORT=9000
45+
SANDBOX_BUN_PORT=3000
46+
SANDBOX_PYTHON_PORT=8000
47+
SANDBOX_PLAN_TEST=false
4248

4349
# GitHub App Authentication
4450
# ------------------------------------------------------------------------------------

.env.prod.public

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/projects
1212
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
1313
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/projects
1414

15-
# AI Models
16-
# ------------------------------------------------------------------------------------
17-
NEXT_PUBLIC_DEFAULT_MODEL=claude-haiku-4-5
18-
1915
# Ghost
2016
# ------------------------------------------------------------------------------------
2117
NEXT_PUBLIC_GHOST_CONTENT_API_KEY=8541e6ab3021f8dbb7a329e2fe

.env.stage

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ CLERK_SECRET_KEY=${CLERK_SECRET_KEY}
1414
# AI Provider
1515
# ------------------------------------------------------------------------------------
1616
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
17+
ANTHROPIC_MODEL=claude-sonnet-4-5-20250929
18+
GOOGLE_API_KEY=${GOOGLE_API_KEY}
19+
GOOGLE_MODEL=gemini-2.0-flash
1720
AGENT_MAX_TURNS=25
1821

1922
# Docker Related
@@ -39,6 +42,9 @@ SANDBOX_MEMORY_LIMIT=4294967296 # 4GB
3942
SANDBOX_CPU_SHARES=512
4043
SANDBOX_PIDS_LIMIT=4096
4144
SANDBOX_AGENT_PORT=9000
45+
SANDBOX_BUN_PORT=3000
46+
SANDBOX_PYTHON_PORT=8000
47+
SANDBOX_PLAN_TEST=false
4248

4349
# GitHub App Authentication
4450
# ------------------------------------------------------------------------------------

.env.stage.public

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL=/projects
1212
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
1313
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL=/projects
1414

15-
# AI Models
16-
# ------------------------------------------------------------------------------------
17-
NEXT_PUBLIC_DEFAULT_MODEL=claude-haiku-4-5
18-
1915
# Ghost
2016
# ------------------------------------------------------------------------------------
2117
NEXT_PUBLIC_GHOST_CONTENT_API_KEY=

.github/workflows/build-sandbox-image.yml

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,6 @@ jobs:
2424
- name: Checkout repository
2525
uses: actions/checkout@v6
2626

27-
- name: Set up Bun
28-
uses: oven-sh/setup-bun@v2
29-
with:
30-
bun-version: '1.3.1'
31-
32-
- name: Build Agent Server
33-
run: |
34-
cd sandbox/agent
35-
bun install --frozen-lockfile
36-
bun run build
37-
3827
- name: Set up Docker Buildx
3928
uses: docker/setup-buildx-action@v3
4029

@@ -65,6 +54,11 @@ jobs:
6554
cache-from: type=gha
6655
cache-to: type=gha,mode=max
6756
platforms: linux/amd64,linux/arm64
57+
build-args: |
58+
KOSUKE_CLI_MODE=production
59+
INSTALL_CHROMIUM=false
60+
secrets: |
61+
npm_token=${{ secrets.GITHUB_TOKEN }}
6862
6963
- name: Notify Slack - Success
7064
if: success()

.github/workflows/ci.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,13 @@ jobs:
2323
uses: actions/cache@v5
2424
with:
2525
path: ~/.bun/install/cache
26-
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'sandbox/agent/bun.lock') }}
26+
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
2727
restore-keys: |
2828
${{ runner.os }}-bun-
2929
3030
- name: Install dependencies
3131
run: bun install
3232

33-
- name: Install agent dependencies
34-
run: cd sandbox/agent && bun install
35-
3633
- name: Copy environment variables
3734
run: cp .env.local .env
3835

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@ public/uploads/*
6262
# sandbox agent
6363
sandbox/agent/node_modules/
6464
sandbox/agent/dist/
65+
66+
# kosuke-cli - separate git repo (nested inside sandbox/)
67+
sandbox/kosuke-cli/

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dist/
1414
templates/
1515
roo-code/
1616
venv/
17+
sandbox/kosuke-cli/
1718

1819
# Cache
1920
.npm

0 commit comments

Comments
 (0)